├── .dockerignore
├── .editorconfig
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .travis.yml
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── bin
└── server.js
├── layers
└── mathjax-node-layer
│ ├── .gitignore
│ └── nodejs
│ ├── package-lock.json
│ └── package.json
├── logo.svg
├── package.json
├── src
├── app.js
└── render
│ └── index.js
├── templates
└── root.yml
├── test
├── app.js
└── render
│ └── index.js
└── yarn.lock
/.dockerignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /layers/mathjax-node-layer
3 | /templates
4 | /template.yml
5 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | ; This file is for unifying the coding style for different editors and IDEs.
2 | ; More information at http://editorconfig.org
3 |
4 | root = true
5 |
6 | [*]
7 | indent_style = space
8 | indent_size = 4
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [*.yml]
16 | indent_size = 2
17 |
18 | [Makefile]
19 | indent_style = tab
20 | tab_width = 8
21 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: math-api CI
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | strategy:
13 | matrix:
14 | node: ['12', '14', '16']
15 | steps:
16 | - name: Checkout source code
17 | uses: actions/checkout@v2
18 | - name: Setup Node ${{ matrix.node }} environment
19 | uses: actions/setup-node@v2
20 | with:
21 | node-version: ${{ matrix.node }}
22 | - name: Install dependencies
23 | run: |
24 | npm install yarn
25 | yarn install
26 | - name: Run tests
27 | run: yarn run test
28 |
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /template.yml
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | stages:
2 | - name: test
3 | - name: deploy
4 | if: repo = chialab/math-api AND branch = master AND type IN (push, api)
5 |
6 | jobs:
7 | include:
8 | - stage: 'test'
9 | sudo: false
10 | language: 'node_js'
11 | node_js: '8.10'
12 | install:
13 | - 'yarn install'
14 | script:
15 | - 'yarn run test'
16 | - stage: 'test'
17 | sudo: false
18 | language: 'python'
19 | python: '3.6'
20 | install:
21 | - 'pip install awscli'
22 | script:
23 | - 'make validate'
24 |
25 | - stage: 'deploy'
26 | sudo: 'required'
27 | language: 'python'
28 | python: '3.6'
29 | services:
30 | - 'docker'
31 | install:
32 | - 'pip install awscli'
33 | - 'docker image pull lambci/lambda:build-nodejs8.10'
34 | script: 'skip'
35 | before_deploy:
36 | - 'make layer'
37 | deploy:
38 | # Deploy templates in `s3://thebucket/org/repo/branch-name`.
39 | skip_cleanup: true
40 | provider: 'script'
41 | script: 'make package S3_BUCKET=$S3_BUCKET S3_PREFIX="$TRAVIS_REPO_SLUG/$TRAVIS_BRANCH"'
42 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | ARG NODE_VERSION=8.10
2 | FROM node:${NODE_VERSION}
3 |
4 | ARG NODE_ENV=production
5 | ENV PORT=3000 NODE_ENV=${NODE_ENV}
6 |
7 | WORKDIR /usr/src/app
8 | COPY package.json yarn.lock /usr/src/app/
9 | RUN yarn install
10 | COPY . /usr/src/app
11 |
12 | EXPOSE 3000
13 |
14 | CMD [ "yarn", "start" ]
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Chialab
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | ALL: deploy
2 | .PHONY: layers deploy package validate
3 |
4 | PROJECT := Math
5 | ENVIRONMENT ?= Test
6 | STACK_NAME ?= MathApi$(ENVIRONMENT)
7 | PACKAGE_TEMPLATE := template.yml
8 | PACKAGE_BUCKET ?= chialab-cloudformation-templates
9 | PACKAGE_PREFIX ?= chialab/math-api/$(shell git symbolic-ref --short HEAD)
10 |
11 | PACKAGE_PROFILE ?= chialabsrl
12 | DEPLOY_PROFILE ?= chialab
13 |
14 | layers:
15 | docker run --rm \
16 | -v $(PWD)/layers/mathjax-node-layer/nodejs:/var/task \
17 | -e NODE_ENV=production \
18 | lambci/lambda:build-nodejs8.10 \
19 | npm $(if $(wildcard layers/mathjax-node-layer/nodejs/node_modules/*), rebuild, install)
20 |
21 | ensure-layer-%:
22 | @if ! [[ -d 'layers/$*/nodejs/node_modules' ]]; then \
23 | printf '\033[31mDependencies for layer \033[1m%s\033[22m are not installed, run \033[1m%s\033[22m first!\033[0m\n' $* 'make layers'; \
24 | exit 1; \
25 | fi
26 |
27 | deploy: package
28 | aws cloudformation deploy \
29 | --template-file $(PACKAGE_TEMPLATE) \
30 | --stack-name $(STACK_NAME) \
31 | --tags Project=$(PROJECT) Environment=$(ENVIRONMENT) \
32 | --capabilities CAPABILITY_IAM \
33 | --profile $(DEPLOY_PROFILE)
34 |
35 | package: ensure-layer-mathjax-node-layer validate
36 | aws cloudformation package \
37 | --template-file templates/root.yml \
38 | --output-template-file $(PACKAGE_TEMPLATE) \
39 | --s3-bucket $(PACKAGE_BUCKET) \
40 | --s3-prefix $(PACKAGE_PREFIX) \
41 | --profile $(PACKAGE_PROFILE)
42 | aws s3 cp $(PACKAGE_TEMPLATE) s3://$(PACKAGE_BUCKET)/$(PACKAGE_PREFIX)/ --profile $(PACKAGE_PROFILE)
43 | @echo "https://$(PACKAGE_BUCKET).s3.amazonaws.com/$(PACKAGE_PREFIX)/$(PACKAGE_TEMPLATE)"
44 |
45 | validate:
46 | aws cloudformation validate-template \
47 | --template-body file://templates/root.yml \
48 | --profile $(PACKAGE_PROFILE)
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Math API • Render LaTeX and MathML formulas as SVG or PNG.
9 |
10 |
11 | ---
12 |
13 | A REST API to do fancy things with formulas, like rendering LaTeX or MathML to
14 | SVG or PNG on the server side using [MathJax for Node](https://github.com/mathjax/MathJax-node),
15 | while leveraging expensive computations on the client.
16 |
17 | ## As a Serverless application
18 |
19 | You can deploy this repository as a serverless application using an AWS CloudFormation
20 | Template to create an AWS API Gateway that invokes Lambda functions to serve requests.
21 |
22 | > [**Launch this stack on AWS**](https://console.aws.amazon.com/cloudformation/home#/stacks/new?stackName=MathApi&templateURL=https://chialab-cloudformation-templates.s3-eu-west-1.amazonaws.com/chialab/math-api/master/template.yml)
23 |
24 | ## As a Docker image
25 |
26 | You can pull and run a Docker container to deploy the API on your local machine,
27 | server, Kubernetes cluster, whatever!
28 |
29 | To start the container (it will bind on ):
30 |
31 | ```sh
32 | docker run --name math-api -d -p 3000:3000 chialab/math-api
33 | ```
34 |
35 | ## Endpoints
36 |
37 | - [`GET /render`](#get-render)
38 | - [`POST /render`](#post-render)
39 |
40 | ### `GET /render`
41 |
42 | > An endpoint to render LaTeX and MathML formulas to SVG or PNG.
43 |
44 | **Query parameters**:
45 |
46 | - `input` (**required**): the format of math in input.
47 | **Valid values**: `latex`, `mathml`
48 | - `inline` (_optional_): when `input` is `latex`, optionally enable "inline" mode.
49 | **Valid values**: `0`, `1`
50 | - `source` (**required**): the math to be rendered.
51 | **Valid values**: _string, depends on the input type_
52 | - `output` (**required**): the output format.
53 | **Valid values**: `mathml`, `png`, `svg`
54 | - `width`, `height` (_optional_): when `output` is `png`, specify the dimensions of the image to return.
55 | **Valid values**: _positive integers_
56 |
57 | **Examples**:
58 |
59 | ```http
60 | GET /render?input=latex&output=svg&source=x^2 HTTP/1.1
61 | Accept: image/svg+xml
62 | ```
63 |
64 | ```http
65 | GET /render?input=latex&inline=1&output=png&source=x^2&width=512 HTTP/1.1
66 | Accept: image/png
67 | ```
68 |
69 | ### `POST /render`
70 |
71 | > An endpoint to render LaTeX and MathML formulas to SVG or PNG.
72 |
73 | **Request body (JSON)**:
74 |
75 | - `input` (**required**): the format of math in input.
76 | **Valid values**: `latex`, `mathml`
77 | - `inline` (_optional_): when `input` is `latex`, optionally enable "inline" mode.
78 | **Valid values**: _boolean_
79 | - `source` (**required**): the math to be rendered.
80 | **Valid values**: _string, depends on the input type_
81 | - `output` (**required**): the output format.
82 | **Valid values**: `mathml`, `png`, `svg`
83 | - `width`, `height` (_optional_): when `output` is `png`, specify the dimensions of the image to return.
84 | **Valid values**: _positive integers_
85 |
86 | **Examples**:
87 |
88 | ```http
89 | POST /render
90 | Accept: image/svg+xml
91 | Content-Type: application/json
92 |
93 | {
94 | "input": "latex",
95 | "output": "svg",
96 | "source": "e^{i \\pi} + 1 = 0"
97 | }
98 | ```
99 |
100 | ```http
101 | POST /render
102 | Accept: image/png
103 | Content-Type: application/json
104 |
105 | {
106 | "input": "latex",
107 | "inline": true,
108 | "output": "png",
109 | "source": "e^{i \\pi} + 1 = 0",
110 | "width": 512
111 | }
112 | ```
113 |
114 | ## Development
115 |
116 | _All the following instructions assume you have at least [NodeJS](https://nodejs.org/) and [Yarn](https://yarnpkg.com/) installed._
117 |
118 | **Start the application locally**:
119 | > `yarn start`
120 |
121 | **Run unit tests**:
122 | > `yarn run test`
123 |
124 | **Start a simulated AWS API Gateway** (_provided you have AWS SAM Local and Docker installed_):
125 | > `yarn run api-gateway`
126 |
127 | **Validate CloudFormation template** (_provided you have AWS CLI installed_)
128 | > `make validate`
129 |
130 | **Package CloudFormation template** (_provided you have AWS CLI and Docker installed_)
131 | > `make layers` (_this is needed only the first time, then when updating MathJax version_)
132 | > `make package`
133 |
134 | **Deploy CloudFormation template** (_provided you have AWS CLI and Docker installed_)
135 | > `make deploy`
136 | > `make deploy ENVIRONMENT=Production`
137 |
138 | ---
139 |
140 | ## License
141 |
142 | Math API is released under the [MIT](https://github.com/chialab/math-api/blob/master/LICENSE) license.
143 |
--------------------------------------------------------------------------------
/bin/server.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const port = process.env.PORT || 3000;
4 | const app = require('../src/app.js');
5 |
6 | app.listen(port, () => console.log(`Server running at http://localhost:${port}/`));
7 |
--------------------------------------------------------------------------------
/layers/mathjax-node-layer/.gitignore:
--------------------------------------------------------------------------------
1 | /nodejs/node_modules
2 |
--------------------------------------------------------------------------------
/layers/mathjax-node-layer/nodejs/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "requires": true,
3 | "lockfileVersion": 1,
4 | "dependencies": {
5 | "abab": {
6 | "version": "2.0.0",
7 | "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz",
8 | "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w=="
9 | },
10 | "acorn": {
11 | "version": "5.7.3",
12 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz",
13 | "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw=="
14 | },
15 | "acorn-globals": {
16 | "version": "4.3.2",
17 | "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.2.tgz",
18 | "integrity": "sha512-BbzvZhVtZP+Bs1J1HcwrQe8ycfO0wStkSGxuul3He3GkHOIZ6eTqOkPuw9IP1X3+IkOo4wiJmwkobzXYz4wewQ==",
19 | "requires": {
20 | "acorn": "^6.0.1",
21 | "acorn-walk": "^6.0.1"
22 | },
23 | "dependencies": {
24 | "acorn": {
25 | "version": "6.4.1",
26 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
27 | "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA=="
28 | }
29 | }
30 | },
31 | "acorn-walk": {
32 | "version": "6.1.1",
33 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz",
34 | "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw=="
35 | },
36 | "ajv": {
37 | "version": "6.10.0",
38 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
39 | "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==",
40 | "requires": {
41 | "fast-deep-equal": "^2.0.1",
42 | "fast-json-stable-stringify": "^2.0.0",
43 | "json-schema-traverse": "^0.4.1",
44 | "uri-js": "^4.2.2"
45 | }
46 | },
47 | "ansi-regex": {
48 | "version": "2.1.1",
49 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
50 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
51 | },
52 | "array-equal": {
53 | "version": "1.0.0",
54 | "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
55 | "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM="
56 | },
57 | "asn1": {
58 | "version": "0.2.4",
59 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
60 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
61 | "requires": {
62 | "safer-buffer": "~2.1.0"
63 | }
64 | },
65 | "assert-plus": {
66 | "version": "1.0.0",
67 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
68 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
69 | },
70 | "async-limiter": {
71 | "version": "1.0.0",
72 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
73 | "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
74 | },
75 | "asynckit": {
76 | "version": "0.4.0",
77 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
78 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
79 | },
80 | "aws-sign2": {
81 | "version": "0.7.0",
82 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
83 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
84 | },
85 | "aws4": {
86 | "version": "1.8.0",
87 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
88 | "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
89 | },
90 | "bcrypt-pbkdf": {
91 | "version": "1.0.2",
92 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
93 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
94 | "requires": {
95 | "tweetnacl": "^0.14.3"
96 | }
97 | },
98 | "browser-process-hrtime": {
99 | "version": "0.1.3",
100 | "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz",
101 | "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw=="
102 | },
103 | "buffer-from": {
104 | "version": "1.1.1",
105 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
106 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
107 | },
108 | "camelcase": {
109 | "version": "3.0.0",
110 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
111 | "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo="
112 | },
113 | "caseless": {
114 | "version": "0.12.0",
115 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
116 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
117 | },
118 | "cliui": {
119 | "version": "3.2.0",
120 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
121 | "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
122 | "requires": {
123 | "string-width": "^1.0.1",
124 | "strip-ansi": "^3.0.1",
125 | "wrap-ansi": "^2.0.0"
126 | }
127 | },
128 | "code-point-at": {
129 | "version": "1.1.0",
130 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
131 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
132 | },
133 | "combined-stream": {
134 | "version": "1.0.7",
135 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz",
136 | "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==",
137 | "requires": {
138 | "delayed-stream": "~1.0.0"
139 | }
140 | },
141 | "concat-stream": {
142 | "version": "1.6.2",
143 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
144 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
145 | "requires": {
146 | "buffer-from": "^1.0.0",
147 | "inherits": "^2.0.3",
148 | "readable-stream": "^2.2.2",
149 | "typedarray": "^0.0.6"
150 | }
151 | },
152 | "core-util-is": {
153 | "version": "1.0.2",
154 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
155 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
156 | },
157 | "cssom": {
158 | "version": "0.3.6",
159 | "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz",
160 | "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A=="
161 | },
162 | "cssstyle": {
163 | "version": "1.2.2",
164 | "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.2.2.tgz",
165 | "integrity": "sha512-43wY3kl1CVQSvL7wUY1qXkxVGkStjpkDmVjiIKX8R97uhajy8Bybay78uOtqvh7Q5GK75dNPfW0geWjE6qQQow==",
166 | "requires": {
167 | "cssom": "0.3.x"
168 | }
169 | },
170 | "dashdash": {
171 | "version": "1.14.1",
172 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
173 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
174 | "requires": {
175 | "assert-plus": "^1.0.0"
176 | }
177 | },
178 | "data-urls": {
179 | "version": "1.1.0",
180 | "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz",
181 | "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==",
182 | "requires": {
183 | "abab": "^2.0.0",
184 | "whatwg-mimetype": "^2.2.0",
185 | "whatwg-url": "^7.0.0"
186 | },
187 | "dependencies": {
188 | "whatwg-url": {
189 | "version": "7.0.0",
190 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz",
191 | "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==",
192 | "requires": {
193 | "lodash.sortby": "^4.7.0",
194 | "tr46": "^1.0.1",
195 | "webidl-conversions": "^4.0.2"
196 | }
197 | }
198 | }
199 | },
200 | "debug": {
201 | "version": "2.6.9",
202 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
203 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
204 | "requires": {
205 | "ms": "2.0.0"
206 | }
207 | },
208 | "decamelize": {
209 | "version": "1.2.0",
210 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
211 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
212 | },
213 | "deep-is": {
214 | "version": "0.1.3",
215 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
216 | "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ="
217 | },
218 | "delayed-stream": {
219 | "version": "1.0.0",
220 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
221 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
222 | },
223 | "domexception": {
224 | "version": "1.0.1",
225 | "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz",
226 | "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==",
227 | "requires": {
228 | "webidl-conversions": "^4.0.2"
229 | }
230 | },
231 | "ecc-jsbn": {
232 | "version": "0.1.2",
233 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
234 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
235 | "requires": {
236 | "jsbn": "~0.1.0",
237 | "safer-buffer": "^2.1.0"
238 | }
239 | },
240 | "error-ex": {
241 | "version": "1.3.2",
242 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
243 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
244 | "requires": {
245 | "is-arrayish": "^0.2.1"
246 | }
247 | },
248 | "es6-promise": {
249 | "version": "4.2.6",
250 | "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.6.tgz",
251 | "integrity": "sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q=="
252 | },
253 | "escodegen": {
254 | "version": "1.11.1",
255 | "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz",
256 | "integrity": "sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==",
257 | "requires": {
258 | "esprima": "^3.1.3",
259 | "estraverse": "^4.2.0",
260 | "esutils": "^2.0.2",
261 | "optionator": "^0.8.1",
262 | "source-map": "~0.6.1"
263 | }
264 | },
265 | "esprima": {
266 | "version": "3.1.3",
267 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
268 | "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM="
269 | },
270 | "estraverse": {
271 | "version": "4.2.0",
272 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
273 | "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM="
274 | },
275 | "esutils": {
276 | "version": "2.0.2",
277 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
278 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
279 | },
280 | "extend": {
281 | "version": "3.0.2",
282 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
283 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
284 | },
285 | "extract-zip": {
286 | "version": "1.6.7",
287 | "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz",
288 | "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=",
289 | "requires": {
290 | "concat-stream": "1.6.2",
291 | "debug": "2.6.9",
292 | "mkdirp": "0.5.1",
293 | "yauzl": "2.4.1"
294 | }
295 | },
296 | "extsprintf": {
297 | "version": "1.3.0",
298 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
299 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
300 | },
301 | "fast-deep-equal": {
302 | "version": "2.0.1",
303 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
304 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
305 | },
306 | "fast-json-stable-stringify": {
307 | "version": "2.0.0",
308 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
309 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
310 | },
311 | "fast-levenshtein": {
312 | "version": "2.0.6",
313 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
314 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
315 | },
316 | "fd-slicer": {
317 | "version": "1.0.1",
318 | "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
319 | "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=",
320 | "requires": {
321 | "pend": "~1.2.0"
322 | }
323 | },
324 | "file-url": {
325 | "version": "2.0.2",
326 | "resolved": "https://registry.npmjs.org/file-url/-/file-url-2.0.2.tgz",
327 | "integrity": "sha1-6VF4TXkJUSfTcTApqwY/QIGMoq4="
328 | },
329 | "find-up": {
330 | "version": "1.1.2",
331 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
332 | "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
333 | "requires": {
334 | "path-exists": "^2.0.0",
335 | "pinkie-promise": "^2.0.0"
336 | }
337 | },
338 | "forever-agent": {
339 | "version": "0.6.1",
340 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
341 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
342 | },
343 | "form-data": {
344 | "version": "2.3.3",
345 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
346 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
347 | "requires": {
348 | "asynckit": "^0.4.0",
349 | "combined-stream": "^1.0.6",
350 | "mime-types": "^2.1.12"
351 | }
352 | },
353 | "fs-extra": {
354 | "version": "1.0.0",
355 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz",
356 | "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=",
357 | "requires": {
358 | "graceful-fs": "^4.1.2",
359 | "jsonfile": "^2.1.0",
360 | "klaw": "^1.0.0"
361 | }
362 | },
363 | "get-caller-file": {
364 | "version": "1.0.3",
365 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
366 | "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w=="
367 | },
368 | "getpass": {
369 | "version": "0.1.7",
370 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
371 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
372 | "requires": {
373 | "assert-plus": "^1.0.0"
374 | }
375 | },
376 | "graceful-fs": {
377 | "version": "4.1.15",
378 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
379 | "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA=="
380 | },
381 | "har-schema": {
382 | "version": "2.0.0",
383 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
384 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
385 | },
386 | "har-validator": {
387 | "version": "5.1.3",
388 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
389 | "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
390 | "requires": {
391 | "ajv": "^6.5.5",
392 | "har-schema": "^2.0.0"
393 | }
394 | },
395 | "hasha": {
396 | "version": "2.2.0",
397 | "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz",
398 | "integrity": "sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE=",
399 | "requires": {
400 | "is-stream": "^1.0.1",
401 | "pinkie-promise": "^2.0.0"
402 | }
403 | },
404 | "hosted-git-info": {
405 | "version": "2.7.1",
406 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
407 | "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w=="
408 | },
409 | "html-encoding-sniffer": {
410 | "version": "1.0.2",
411 | "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz",
412 | "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==",
413 | "requires": {
414 | "whatwg-encoding": "^1.0.1"
415 | }
416 | },
417 | "http-signature": {
418 | "version": "1.2.0",
419 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
420 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
421 | "requires": {
422 | "assert-plus": "^1.0.0",
423 | "jsprim": "^1.2.2",
424 | "sshpk": "^1.7.0"
425 | }
426 | },
427 | "iconv-lite": {
428 | "version": "0.4.24",
429 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
430 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
431 | "requires": {
432 | "safer-buffer": ">= 2.1.2 < 3"
433 | }
434 | },
435 | "inherits": {
436 | "version": "2.0.3",
437 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
438 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
439 | },
440 | "invert-kv": {
441 | "version": "1.0.0",
442 | "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
443 | "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY="
444 | },
445 | "is-arrayish": {
446 | "version": "0.2.1",
447 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
448 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
449 | },
450 | "is-fullwidth-code-point": {
451 | "version": "2.0.0",
452 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
453 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
454 | },
455 | "is-stream": {
456 | "version": "1.1.0",
457 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
458 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
459 | },
460 | "is-typedarray": {
461 | "version": "1.0.0",
462 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
463 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
464 | },
465 | "is-utf8": {
466 | "version": "0.2.1",
467 | "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
468 | "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI="
469 | },
470 | "isarray": {
471 | "version": "1.0.0",
472 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
473 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
474 | },
475 | "isexe": {
476 | "version": "2.0.0",
477 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
478 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
479 | },
480 | "isstream": {
481 | "version": "0.1.2",
482 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
483 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
484 | },
485 | "jsbn": {
486 | "version": "0.1.1",
487 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
488 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
489 | },
490 | "jsdom": {
491 | "version": "11.12.0",
492 | "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz",
493 | "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==",
494 | "requires": {
495 | "abab": "^2.0.0",
496 | "acorn": "^5.5.3",
497 | "acorn-globals": "^4.1.0",
498 | "array-equal": "^1.0.0",
499 | "cssom": ">= 0.3.2 < 0.4.0",
500 | "cssstyle": "^1.0.0",
501 | "data-urls": "^1.0.0",
502 | "domexception": "^1.0.1",
503 | "escodegen": "^1.9.1",
504 | "html-encoding-sniffer": "^1.0.2",
505 | "left-pad": "^1.3.0",
506 | "nwsapi": "^2.0.7",
507 | "parse5": "4.0.0",
508 | "pn": "^1.1.0",
509 | "request": "^2.87.0",
510 | "request-promise-native": "^1.0.5",
511 | "sax": "^1.2.4",
512 | "symbol-tree": "^3.2.2",
513 | "tough-cookie": "^2.3.4",
514 | "w3c-hr-time": "^1.0.1",
515 | "webidl-conversions": "^4.0.2",
516 | "whatwg-encoding": "^1.0.3",
517 | "whatwg-mimetype": "^2.1.0",
518 | "whatwg-url": "^6.4.1",
519 | "ws": "^5.2.0",
520 | "xml-name-validator": "^3.0.0"
521 | }
522 | },
523 | "json-schema": {
524 | "version": "0.2.3",
525 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
526 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
527 | },
528 | "json-schema-traverse": {
529 | "version": "0.4.1",
530 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
531 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
532 | },
533 | "json-stringify-safe": {
534 | "version": "5.0.1",
535 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
536 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
537 | },
538 | "jsonfile": {
539 | "version": "2.4.0",
540 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
541 | "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
542 | "requires": {
543 | "graceful-fs": "^4.1.6"
544 | }
545 | },
546 | "jsprim": {
547 | "version": "1.4.1",
548 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
549 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
550 | "requires": {
551 | "assert-plus": "1.0.0",
552 | "extsprintf": "1.3.0",
553 | "json-schema": "0.2.3",
554 | "verror": "1.10.0"
555 | }
556 | },
557 | "kew": {
558 | "version": "0.7.0",
559 | "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz",
560 | "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s="
561 | },
562 | "klaw": {
563 | "version": "1.3.1",
564 | "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz",
565 | "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=",
566 | "requires": {
567 | "graceful-fs": "^4.1.9"
568 | }
569 | },
570 | "lcid": {
571 | "version": "1.0.0",
572 | "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
573 | "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
574 | "requires": {
575 | "invert-kv": "^1.0.0"
576 | }
577 | },
578 | "left-pad": {
579 | "version": "1.3.0",
580 | "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz",
581 | "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="
582 | },
583 | "levn": {
584 | "version": "0.3.0",
585 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
586 | "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
587 | "requires": {
588 | "prelude-ls": "~1.1.2",
589 | "type-check": "~0.3.2"
590 | }
591 | },
592 | "load-json-file": {
593 | "version": "1.1.0",
594 | "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
595 | "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
596 | "requires": {
597 | "graceful-fs": "^4.1.2",
598 | "parse-json": "^2.2.0",
599 | "pify": "^2.0.0",
600 | "pinkie-promise": "^2.0.0",
601 | "strip-bom": "^2.0.0"
602 | }
603 | },
604 | "lodash": {
605 | "version": "4.17.15",
606 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
607 | "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
608 | },
609 | "lodash.sortby": {
610 | "version": "4.7.0",
611 | "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
612 | "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg="
613 | },
614 | "mathjax": {
615 | "version": "2.7.5",
616 | "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-2.7.5.tgz",
617 | "integrity": "sha512-OzsJNitEHAJB3y4IIlPCAvS0yoXwYjlo2Y4kmm9KQzyIBZt2d8yKRalby3uTRNN4fZQiGL2iMXjpdP1u2Rq2DQ=="
618 | },
619 | "mathjax-node": {
620 | "version": "2.1.1",
621 | "resolved": "https://registry.npmjs.org/mathjax-node/-/mathjax-node-2.1.1.tgz",
622 | "integrity": "sha1-JcgPSU91QEGP/Pqcx1bf0hUCAb0=",
623 | "requires": {
624 | "is-fullwidth-code-point": "^2.0.0",
625 | "jsdom": "^11.0.0",
626 | "mathjax": "^2.7.2"
627 | }
628 | },
629 | "mime-db": {
630 | "version": "1.40.0",
631 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
632 | "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
633 | },
634 | "mime-types": {
635 | "version": "2.1.24",
636 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
637 | "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
638 | "requires": {
639 | "mime-db": "1.40.0"
640 | }
641 | },
642 | "minimist": {
643 | "version": "0.0.8",
644 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
645 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
646 | },
647 | "mkdirp": {
648 | "version": "0.5.1",
649 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
650 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
651 | "requires": {
652 | "minimist": "0.0.8"
653 | }
654 | },
655 | "ms": {
656 | "version": "2.0.0",
657 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
658 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
659 | },
660 | "normalize-package-data": {
661 | "version": "2.5.0",
662 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
663 | "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
664 | "requires": {
665 | "hosted-git-info": "^2.1.4",
666 | "resolve": "^1.10.0",
667 | "semver": "2 || 3 || 4 || 5",
668 | "validate-npm-package-license": "^3.0.1"
669 | }
670 | },
671 | "number-is-nan": {
672 | "version": "1.0.1",
673 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
674 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
675 | },
676 | "nwsapi": {
677 | "version": "2.1.4",
678 | "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.4.tgz",
679 | "integrity": "sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw=="
680 | },
681 | "oauth-sign": {
682 | "version": "0.9.0",
683 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
684 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
685 | },
686 | "optionator": {
687 | "version": "0.8.2",
688 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
689 | "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
690 | "requires": {
691 | "deep-is": "~0.1.3",
692 | "fast-levenshtein": "~2.0.4",
693 | "levn": "~0.3.0",
694 | "prelude-ls": "~1.1.2",
695 | "type-check": "~0.3.2",
696 | "wordwrap": "~1.0.0"
697 | }
698 | },
699 | "os-locale": {
700 | "version": "1.4.0",
701 | "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
702 | "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
703 | "requires": {
704 | "lcid": "^1.0.0"
705 | }
706 | },
707 | "parse-json": {
708 | "version": "2.2.0",
709 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
710 | "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
711 | "requires": {
712 | "error-ex": "^1.2.0"
713 | }
714 | },
715 | "parse5": {
716 | "version": "4.0.0",
717 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz",
718 | "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA=="
719 | },
720 | "path-exists": {
721 | "version": "2.1.0",
722 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
723 | "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
724 | "requires": {
725 | "pinkie-promise": "^2.0.0"
726 | }
727 | },
728 | "path-parse": {
729 | "version": "1.0.6",
730 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
731 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
732 | },
733 | "path-type": {
734 | "version": "1.1.0",
735 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
736 | "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
737 | "requires": {
738 | "graceful-fs": "^4.1.2",
739 | "pify": "^2.0.0",
740 | "pinkie-promise": "^2.0.0"
741 | }
742 | },
743 | "pend": {
744 | "version": "1.2.0",
745 | "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
746 | "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA="
747 | },
748 | "performance-now": {
749 | "version": "2.1.0",
750 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
751 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
752 | },
753 | "phantomjs-prebuilt": {
754 | "version": "2.1.16",
755 | "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz",
756 | "integrity": "sha1-79ISpKOWbTZHaE6ouniFSb4q7+8=",
757 | "requires": {
758 | "es6-promise": "^4.0.3",
759 | "extract-zip": "^1.6.5",
760 | "fs-extra": "^1.0.0",
761 | "hasha": "^2.2.0",
762 | "kew": "^0.7.0",
763 | "progress": "^1.1.8",
764 | "request": "^2.81.0",
765 | "request-progress": "^2.0.1",
766 | "which": "^1.2.10"
767 | }
768 | },
769 | "pify": {
770 | "version": "2.3.0",
771 | "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
772 | "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
773 | },
774 | "pinkie": {
775 | "version": "2.0.4",
776 | "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
777 | "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA="
778 | },
779 | "pinkie-promise": {
780 | "version": "2.0.1",
781 | "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
782 | "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
783 | "requires": {
784 | "pinkie": "^2.0.0"
785 | }
786 | },
787 | "pn": {
788 | "version": "1.1.0",
789 | "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz",
790 | "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA=="
791 | },
792 | "prelude-ls": {
793 | "version": "1.1.2",
794 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
795 | "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
796 | },
797 | "process-nextick-args": {
798 | "version": "2.0.0",
799 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
800 | "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
801 | },
802 | "progress": {
803 | "version": "1.1.8",
804 | "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz",
805 | "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74="
806 | },
807 | "psl": {
808 | "version": "1.1.31",
809 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz",
810 | "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw=="
811 | },
812 | "punycode": {
813 | "version": "2.1.1",
814 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
815 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
816 | },
817 | "qs": {
818 | "version": "6.5.2",
819 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
820 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
821 | },
822 | "read-pkg": {
823 | "version": "1.1.0",
824 | "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
825 | "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
826 | "requires": {
827 | "load-json-file": "^1.0.0",
828 | "normalize-package-data": "^2.3.2",
829 | "path-type": "^1.0.0"
830 | }
831 | },
832 | "read-pkg-up": {
833 | "version": "1.0.1",
834 | "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
835 | "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
836 | "requires": {
837 | "find-up": "^1.0.0",
838 | "read-pkg": "^1.0.0"
839 | }
840 | },
841 | "readable-stream": {
842 | "version": "2.3.6",
843 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
844 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
845 | "requires": {
846 | "core-util-is": "~1.0.0",
847 | "inherits": "~2.0.3",
848 | "isarray": "~1.0.0",
849 | "process-nextick-args": "~2.0.0",
850 | "safe-buffer": "~5.1.1",
851 | "string_decoder": "~1.1.1",
852 | "util-deprecate": "~1.0.1"
853 | }
854 | },
855 | "request": {
856 | "version": "2.88.0",
857 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
858 | "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
859 | "requires": {
860 | "aws-sign2": "~0.7.0",
861 | "aws4": "^1.8.0",
862 | "caseless": "~0.12.0",
863 | "combined-stream": "~1.0.6",
864 | "extend": "~3.0.2",
865 | "forever-agent": "~0.6.1",
866 | "form-data": "~2.3.2",
867 | "har-validator": "~5.1.0",
868 | "http-signature": "~1.2.0",
869 | "is-typedarray": "~1.0.0",
870 | "isstream": "~0.1.2",
871 | "json-stringify-safe": "~5.0.1",
872 | "mime-types": "~2.1.19",
873 | "oauth-sign": "~0.9.0",
874 | "performance-now": "^2.1.0",
875 | "qs": "~6.5.2",
876 | "safe-buffer": "^5.1.2",
877 | "tough-cookie": "~2.4.3",
878 | "tunnel-agent": "^0.6.0",
879 | "uuid": "^3.3.2"
880 | },
881 | "dependencies": {
882 | "punycode": {
883 | "version": "1.4.1",
884 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
885 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
886 | },
887 | "tough-cookie": {
888 | "version": "2.4.3",
889 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
890 | "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
891 | "requires": {
892 | "psl": "^1.1.24",
893 | "punycode": "^1.4.1"
894 | }
895 | }
896 | }
897 | },
898 | "request-progress": {
899 | "version": "2.0.1",
900 | "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz",
901 | "integrity": "sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg=",
902 | "requires": {
903 | "throttleit": "^1.0.0"
904 | }
905 | },
906 | "request-promise-core": {
907 | "version": "1.1.2",
908 | "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz",
909 | "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==",
910 | "requires": {
911 | "lodash": "^4.17.11"
912 | }
913 | },
914 | "request-promise-native": {
915 | "version": "1.0.7",
916 | "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz",
917 | "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==",
918 | "requires": {
919 | "request-promise-core": "1.1.2",
920 | "stealthy-require": "^1.1.1",
921 | "tough-cookie": "^2.3.3"
922 | }
923 | },
924 | "require-directory": {
925 | "version": "2.1.1",
926 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
927 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
928 | },
929 | "require-main-filename": {
930 | "version": "1.0.1",
931 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
932 | "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE="
933 | },
934 | "resolve": {
935 | "version": "1.10.1",
936 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.1.tgz",
937 | "integrity": "sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA==",
938 | "requires": {
939 | "path-parse": "^1.0.6"
940 | }
941 | },
942 | "safe-buffer": {
943 | "version": "5.1.2",
944 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
945 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
946 | },
947 | "safer-buffer": {
948 | "version": "2.1.2",
949 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
950 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
951 | },
952 | "sax": {
953 | "version": "1.2.4",
954 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
955 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
956 | },
957 | "semver": {
958 | "version": "5.7.0",
959 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
960 | "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA=="
961 | },
962 | "set-blocking": {
963 | "version": "2.0.0",
964 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
965 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
966 | },
967 | "source-map": {
968 | "version": "0.6.1",
969 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
970 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
971 | "optional": true
972 | },
973 | "spdx-correct": {
974 | "version": "3.1.0",
975 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
976 | "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
977 | "requires": {
978 | "spdx-expression-parse": "^3.0.0",
979 | "spdx-license-ids": "^3.0.0"
980 | }
981 | },
982 | "spdx-exceptions": {
983 | "version": "2.2.0",
984 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
985 | "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA=="
986 | },
987 | "spdx-expression-parse": {
988 | "version": "3.0.0",
989 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
990 | "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
991 | "requires": {
992 | "spdx-exceptions": "^2.1.0",
993 | "spdx-license-ids": "^3.0.0"
994 | }
995 | },
996 | "spdx-license-ids": {
997 | "version": "3.0.4",
998 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz",
999 | "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA=="
1000 | },
1001 | "sshpk": {
1002 | "version": "1.16.1",
1003 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
1004 | "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
1005 | "requires": {
1006 | "asn1": "~0.2.3",
1007 | "assert-plus": "^1.0.0",
1008 | "bcrypt-pbkdf": "^1.0.0",
1009 | "dashdash": "^1.12.0",
1010 | "ecc-jsbn": "~0.1.1",
1011 | "getpass": "^0.1.1",
1012 | "jsbn": "~0.1.0",
1013 | "safer-buffer": "^2.0.2",
1014 | "tweetnacl": "~0.14.0"
1015 | }
1016 | },
1017 | "stealthy-require": {
1018 | "version": "1.1.1",
1019 | "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
1020 | "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
1021 | },
1022 | "string-width": {
1023 | "version": "1.0.2",
1024 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
1025 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
1026 | "requires": {
1027 | "code-point-at": "^1.0.0",
1028 | "is-fullwidth-code-point": "^1.0.0",
1029 | "strip-ansi": "^3.0.0"
1030 | },
1031 | "dependencies": {
1032 | "is-fullwidth-code-point": {
1033 | "version": "1.0.0",
1034 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
1035 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
1036 | "requires": {
1037 | "number-is-nan": "^1.0.0"
1038 | }
1039 | }
1040 | }
1041 | },
1042 | "string_decoder": {
1043 | "version": "1.1.1",
1044 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
1045 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
1046 | "requires": {
1047 | "safe-buffer": "~5.1.0"
1048 | }
1049 | },
1050 | "strip-ansi": {
1051 | "version": "3.0.1",
1052 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
1053 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
1054 | "requires": {
1055 | "ansi-regex": "^2.0.0"
1056 | }
1057 | },
1058 | "strip-bom": {
1059 | "version": "2.0.0",
1060 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
1061 | "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
1062 | "requires": {
1063 | "is-utf8": "^0.2.0"
1064 | }
1065 | },
1066 | "svg2png": {
1067 | "version": "4.1.1",
1068 | "resolved": "https://registry.npmjs.org/svg2png/-/svg2png-4.1.1.tgz",
1069 | "integrity": "sha1-a54DmKpBh3i2Q24Sei+38A1JnCg=",
1070 | "requires": {
1071 | "file-url": "^2.0.0",
1072 | "phantomjs-prebuilt": "^2.1.14",
1073 | "pn": "^1.0.0",
1074 | "yargs": "^6.5.0"
1075 | }
1076 | },
1077 | "symbol-tree": {
1078 | "version": "3.2.2",
1079 | "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz",
1080 | "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY="
1081 | },
1082 | "throttleit": {
1083 | "version": "1.0.0",
1084 | "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz",
1085 | "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw="
1086 | },
1087 | "tough-cookie": {
1088 | "version": "2.5.0",
1089 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
1090 | "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
1091 | "requires": {
1092 | "psl": "^1.1.28",
1093 | "punycode": "^2.1.1"
1094 | }
1095 | },
1096 | "tr46": {
1097 | "version": "1.0.1",
1098 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
1099 | "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
1100 | "requires": {
1101 | "punycode": "^2.1.0"
1102 | }
1103 | },
1104 | "tunnel-agent": {
1105 | "version": "0.6.0",
1106 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
1107 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
1108 | "requires": {
1109 | "safe-buffer": "^5.0.1"
1110 | }
1111 | },
1112 | "tweetnacl": {
1113 | "version": "0.14.5",
1114 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
1115 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
1116 | },
1117 | "type-check": {
1118 | "version": "0.3.2",
1119 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
1120 | "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
1121 | "requires": {
1122 | "prelude-ls": "~1.1.2"
1123 | }
1124 | },
1125 | "typedarray": {
1126 | "version": "0.0.6",
1127 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
1128 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
1129 | },
1130 | "uri-js": {
1131 | "version": "4.2.2",
1132 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
1133 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
1134 | "requires": {
1135 | "punycode": "^2.1.0"
1136 | }
1137 | },
1138 | "util-deprecate": {
1139 | "version": "1.0.2",
1140 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
1141 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
1142 | },
1143 | "uuid": {
1144 | "version": "3.3.2",
1145 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
1146 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
1147 | },
1148 | "validate-npm-package-license": {
1149 | "version": "3.0.4",
1150 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
1151 | "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
1152 | "requires": {
1153 | "spdx-correct": "^3.0.0",
1154 | "spdx-expression-parse": "^3.0.0"
1155 | }
1156 | },
1157 | "verror": {
1158 | "version": "1.10.0",
1159 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
1160 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
1161 | "requires": {
1162 | "assert-plus": "^1.0.0",
1163 | "core-util-is": "1.0.2",
1164 | "extsprintf": "^1.2.0"
1165 | }
1166 | },
1167 | "w3c-hr-time": {
1168 | "version": "1.0.1",
1169 | "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz",
1170 | "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=",
1171 | "requires": {
1172 | "browser-process-hrtime": "^0.1.2"
1173 | }
1174 | },
1175 | "webidl-conversions": {
1176 | "version": "4.0.2",
1177 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
1178 | "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="
1179 | },
1180 | "whatwg-encoding": {
1181 | "version": "1.0.5",
1182 | "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz",
1183 | "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==",
1184 | "requires": {
1185 | "iconv-lite": "0.4.24"
1186 | }
1187 | },
1188 | "whatwg-mimetype": {
1189 | "version": "2.3.0",
1190 | "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz",
1191 | "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g=="
1192 | },
1193 | "whatwg-url": {
1194 | "version": "6.5.0",
1195 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz",
1196 | "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==",
1197 | "requires": {
1198 | "lodash.sortby": "^4.7.0",
1199 | "tr46": "^1.0.1",
1200 | "webidl-conversions": "^4.0.2"
1201 | }
1202 | },
1203 | "which": {
1204 | "version": "1.3.1",
1205 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
1206 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
1207 | "requires": {
1208 | "isexe": "^2.0.0"
1209 | }
1210 | },
1211 | "which-module": {
1212 | "version": "1.0.0",
1213 | "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz",
1214 | "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8="
1215 | },
1216 | "wordwrap": {
1217 | "version": "1.0.0",
1218 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
1219 | "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
1220 | },
1221 | "wrap-ansi": {
1222 | "version": "2.1.0",
1223 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
1224 | "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
1225 | "requires": {
1226 | "string-width": "^1.0.1",
1227 | "strip-ansi": "^3.0.1"
1228 | }
1229 | },
1230 | "ws": {
1231 | "version": "5.2.2",
1232 | "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz",
1233 | "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==",
1234 | "requires": {
1235 | "async-limiter": "~1.0.0"
1236 | }
1237 | },
1238 | "xml-name-validator": {
1239 | "version": "3.0.0",
1240 | "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
1241 | "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw=="
1242 | },
1243 | "y18n": {
1244 | "version": "3.2.1",
1245 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
1246 | "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE="
1247 | },
1248 | "yargs": {
1249 | "version": "6.6.0",
1250 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz",
1251 | "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=",
1252 | "requires": {
1253 | "camelcase": "^3.0.0",
1254 | "cliui": "^3.2.0",
1255 | "decamelize": "^1.1.1",
1256 | "get-caller-file": "^1.0.1",
1257 | "os-locale": "^1.4.0",
1258 | "read-pkg-up": "^1.0.1",
1259 | "require-directory": "^2.1.1",
1260 | "require-main-filename": "^1.0.1",
1261 | "set-blocking": "^2.0.0",
1262 | "string-width": "^1.0.2",
1263 | "which-module": "^1.0.0",
1264 | "y18n": "^3.2.1",
1265 | "yargs-parser": "^4.2.0"
1266 | }
1267 | },
1268 | "yargs-parser": {
1269 | "version": "4.2.1",
1270 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz",
1271 | "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=",
1272 | "requires": {
1273 | "camelcase": "^3.0.0"
1274 | }
1275 | },
1276 | "yauzl": {
1277 | "version": "2.4.1",
1278 | "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz",
1279 | "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=",
1280 | "requires": {
1281 | "fd-slicer": "~1.0.1"
1282 | }
1283 | }
1284 | }
1285 | }
1286 |
--------------------------------------------------------------------------------
/layers/mathjax-node-layer/nodejs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "mathjax": "^3.2.0",
4 | "svg2png": "^4.1.1"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@chialab/math-api",
3 | "version": "0.2.0",
4 | "description": "Render LaTeX and MathML formulas as SVG or PNG.",
5 | "main": "index.js",
6 | "bin": "bin/server.js",
7 | "scripts": {
8 | "start": "node bin/server.js",
9 | "test": "mocha test/*.js test/**/*.js",
10 | "api-gateway": "sam local start-api --template templates/root.yml"
11 | },
12 | "keywords": [
13 | "mathjax",
14 | "node",
15 | "docker"
16 | ],
17 | "author": "Chia Lab s.r.l.",
18 | "license": "MIT",
19 | "dependencies": {
20 | "body-parser": "^1.19.0",
21 | "express": "^4.16.4",
22 | "mathjax": "^3.2.0",
23 | "svg2png": "^4.1.1"
24 | },
25 | "devDependencies": {
26 | "chai": "^4.2.0",
27 | "mocha": "^6.1.4",
28 | "supertest": "^4.0.2"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/app.js:
--------------------------------------------------------------------------------
1 | const { raw } = require('body-parser');
2 | const express = require('express');
3 | const { handler } = require('./render/index.js');
4 |
5 | /** @typedef {{ resource: string, path: string, httpMethod: string, headers: { [x: string]: string }, queryStringParameters: { [x: string]: string }, pathParameters: { [x: string]: string }, body: string, isBase64Encoded: boolean }} LambdaProxyInput */
6 | /** @typedef {{ statusCode: number, headers: { [x: string]: string }, body: string, isBase64Encoded: boolean }} LambdaProxyOutput */
7 |
8 | /**
9 | * Convert ExpressJS incoming request to a Lambda proxy input event.
10 | *
11 | * @returns {LambdaProxyInput}
12 | */
13 | express.request.constructor.prototype.toLambdaEvent = function () {
14 | return {
15 | resource: this.route.path,
16 | path: this.path,
17 | httpMethod: this.method,
18 | headers: Object.assign({}, this.headers || {}),
19 | // multiValueHeaders: { List of strings containing incoming request headers }
20 | queryStringParameters: Object.assign({}, this.query || {}),
21 | // multiValueQueryStringParameters: { List of query string parameters }
22 | pathParameters: Object.assign({}, this.params || {}),
23 | stageVariables: Object.assign({}, this.app.locals || {}),
24 | requestContext: {}, // TODO
25 | body: this.body,
26 | isBase64Encoded: false,
27 | };
28 | };
29 |
30 | /**
31 | * Build ExpressJS response from Lambda proxy output.
32 | *
33 | * @param {LambdaProxyOutput} res Lambda proxy response.
34 | * @returns {ThisType}
35 | */
36 | express.response.constructor.prototype.fromLambdaResponse = function (res) {
37 | this.status(res.statusCode);
38 | if (res.headers) {
39 | this.set(res.headers);
40 | }
41 | if (res.body) {
42 | let buf = Buffer.from(res.body, res.isBase64Encoded ? 'base64' : 'utf8');
43 | this.set('Content-Length', buf.byteLength);
44 | this.write(buf);
45 | }
46 | this.end();
47 |
48 | return this;
49 | };
50 |
51 | const apigw = new express.Router();
52 |
53 | // Render endpoint.
54 | apigw
55 | .route('/render')
56 | .get(async (req, res, next) => {
57 | try {
58 | res.fromLambdaResponse(await handler(req.toLambdaEvent()));
59 | } catch (err) {
60 | next(err);
61 | }
62 | })
63 | .post(async (req, res, next) => {
64 | try {
65 | res.fromLambdaResponse(await handler(req.toLambdaEvent()));
66 | } catch (err) {
67 | next(err);
68 | }
69 | });
70 |
71 | // Assemble app.
72 | module.exports = express()
73 | .use(raw({ type: '*/*' }))
74 | .use(apigw)
75 | .use((err, req, res, next) => {
76 | // Error handling.
77 | console.error('Integration error', err);
78 |
79 | res.status(500)
80 | .set('Content-Type', 'application/json')
81 | .send(JSON.stringify({ message: 'Internal server error' }));
82 | });
83 |
--------------------------------------------------------------------------------
/src/render/index.js:
--------------------------------------------------------------------------------
1 | const svg2png = require('svg2png');
2 |
3 | /** @typedef {{ input: 'latex', inline?: boolean } | { input: 'mathml' }} InputDefinition */
4 | /** @typedef {{ output: 'mathml' | 'svg' } | { output: 'png', width?: number, height?: number }} OutputDefinition */
5 | /** @typedef {InputDefinition & OutputDefinition & { source: string }} Input */
6 | /** @typedef {'application/mathml+xml' | 'image/png' | 'image/svg+xml'} ContentType */
7 | /** @typedef {{ contentType: ContentType, isBase64Encoded?: boolean, data: string }} Output */
8 |
9 | /** @typedef {{ httpMethod: 'GET' | 'POST', headers: { [x: string]: string }, queryStringParameters: { [x: string]: string }, body: string }} ApiGatewayProxyEvent */
10 | /** @typedef {{ statusCode: number, headers?: { [x: string]: string }, body?: string | Buffer, isBase64Encoded?: boolean }} ApiGatewayProxyResponse */
11 |
12 | /**
13 | * Response types.
14 | *
15 | * @var {{ [x: OutputType]: ContentType }}
16 | */
17 | const RESPONSE_TYPES = {
18 | mathml: 'application/mathml+xml',
19 | png: 'image/png',
20 | svg: 'image/svg+xml',
21 | };
22 |
23 | /**
24 | * MathJax settings.
25 | *
26 | * @var {{}}
27 | */
28 | const defaultConfiguration = {
29 | loader: {
30 | paths: {mathjax: 'mathjax/es5'},
31 | require: require,
32 | load: ['adaptors/liteDOM', 'input/mml', 'input/tex-full', 'output/svg']
33 | },
34 | options: {
35 | enableAssistiveMml: false
36 | }
37 | }
38 | const MJAX_SETTINGS = process.env.MJAX_SETTINGS ? JSON.parse(process.env.MJAX_SETTINGS) : defaultConfiguration;
39 | MathJax = MJAX_SETTINGS;
40 |
41 | require('mathjax/es5/tex-mml-svg.js');
42 |
43 |
44 | /**
45 | * Detect format.
46 | *
47 | * @param {InputDefinition} input Input.
48 | * @returns {'TeX' | 'inline-TeX' | 'MathML'}
49 | */
50 | const getFormat = (input) => {
51 | switch (input.input) {
52 | case 'mathml':
53 | return 'MathML';
54 |
55 | case 'latex':
56 | if (input.inline) {
57 | return 'inline-TeX';
58 | }
59 |
60 | return 'TeX';
61 |
62 | default:
63 | throw new Error(`Invalid input: ${input.input || ''}`);
64 | }
65 | };
66 |
67 | /**
68 | * Typeset math.
69 | *
70 | * @param {{ math: string, format: 'TeX' | 'inline-TeX' | 'MathML', mml?: boolean, svg?: boolean }} data Data.
71 | * @returns {Promise<{ mml?: string, svg?: string }>}
72 | */
73 | const typeset = async (data) => {
74 | try {
75 | await MathJax.startup.promise;
76 | switch (data.format) {
77 | case 'TeX':
78 | case 'inline-TeX':
79 | if (data.mml) {
80 | return MathJax.tex2mmlPromise(data.math);
81 | }
82 | if (data.svg) {
83 | return MathJax.tex2svgPromise(data.math);
84 | }
85 | throw new Error(`Supported output formats for ${data.format} input are: MathML, SVG`);
86 | case 'MathML':
87 | if (data.svg) {
88 | return MathJax.mathml2svgPromise(data.math);
89 | }
90 | throw new Error(`Supported output formats for ${data.format} input are: SVG`);
91 | default:
92 | throw new Error(`Unsupported input format: ${data.format}`);
93 | }
94 | } catch (err) {
95 | console.error('MathJax error', err);
96 |
97 | if (err instanceof Error) {
98 | throw err;
99 | }
100 | if (typeof err === 'string') {
101 | throw new Error(`MathJax error: ${err}`);
102 | }
103 |
104 | // Syntax error.
105 | if (Array.isArray(err) && typeof err[0] === 'string') {
106 | throw new Error(`Invalid source: ${err[0].replace(/[\n\r]+/g, ' ')}`);
107 | }
108 | throw new Error('Invalid source');
109 | }
110 | };
111 |
112 | /**
113 | * Render math.
114 | *
115 | * @param {Input} event Input event.
116 | * @returns {Promise