diff --git a/README.md b/README.md index 005b872a0..ca48db5e8 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ There is a browser-friendly version of this documentation at * [How it works](docs/index.md) * [The Postgres experience on K8s](docs/user.md) +* [The Postgres Operator UI](docs/operator-ui.md) * [DBA options - from RBAC to backup](docs/administrator.md) * [Debug and extend the operator](docs/developer.md) * [Configuration options](docs/reference/operator_parameters.md) diff --git a/delivery.yaml b/delivery.yaml index a60a656b1..683607a13 100644 --- a/delivery.yaml +++ b/delivery.yaml @@ -57,3 +57,38 @@ pipeline: fi export IMAGE make push + + - id: "build-operator-ui" + type: "script" + + commands: + - desc: "Prepare environment" + cmd: | + apt-get update + apt-get install -y build-essential + + - desc: "Compile JavaScript app" + cmd: | + cd ui + make appjs + + - desc: "Build and push Docker image" + cmd: | + cd ui + image_base='registry-write.opensource.zalan.do/acid/postgres-operator-ui' + if [[ "${CDP_TARGET_BRANCH}" == 'master' && -z "${CDP_PULL_REQUEST_NUMBER}" ]] + then + image="${image_base}" + else + image="${image_base}-test" + fi + image_with_tag="${image}:c${CDP_BUILD_VERSION}" + + if docker pull "${image}" + then + docker build --cache-from="${image}" -t "${image_with_tag}" . + else + docker build -t "${image_with_tag}" . + fi + + docker push "${image_with_tag}" diff --git a/docs/administrator.md b/docs/administrator.md index d3cc84251..844a86bf7 100644 --- a/docs/administrator.md +++ b/docs/administrator.md @@ -313,7 +313,7 @@ empty sequence `[]`. Setting the field to `null` or omitting it entirely may lead to K8s removing this field from the manifest due to its [handling of null fields](https://kubernetes.io/docs/concepts/overview/object-management-kubectl/declarative-config/#how-apply-calculates-differences-and-merges-changes). Then the resultant manifest will not contain the necessary change, and the -operator will respectively do noting with the existing source ranges. +operator will respectively do nothing with the existing source ranges. ## Running periodic 'autorepair' scans of K8s objects @@ -409,3 +409,40 @@ A secret can be pre-provisioned in different ways: * Generic secret created via `kubectl create secret generic some-cloud-creds --from-file=some-cloud-credentials-file.json` * Automatically provisioned via a custom K8s controller like [kube-aws-iam-controller](https://github.com/mikkeloscar/kube-aws-iam-controller) + +## Setting up the Postgres Operator UI + +With the v1.2 release the Postgres Operator is shipped with a browser-based +configuration user interface (UI) that simplifies managing Postgres clusters +with the operator. The UI runs with Node.js and comes with it's own docker +image. + +Run NPM to continuously compile `tags/js` code. Basically, it creates an +`app.js` file in: `static/build/app.js` + +``` +(cd ui/app && npm start) +``` + +To build the Docker image open a shell and change to the `ui` folder. Then run: + +``` +docker build -t registry.opensource.zalan.do/acid/postgres-operator-ui:v1.2.0 . +``` + +Apply all manifests for the `ui/manifests` folder to deploy the Postgres +Operator UI on K8s. For local tests you don't need the Ingress resource. + +``` +kubectl apply -f ui/manifests +``` + +Make sure the pods for the operator and the UI are both running. For local +testing you need to apply proxying and port forwarding so that the UI can talk +to the K8s and Postgres Operator REST API. You can use the provided +`run_local.sh` script for this. Make sure it uses the correct URL to your K8s +API server, e.g. for minikube it would be `https://192.168.99.100:8443`. + +``` +./run_local.sh +``` diff --git a/docs/diagrams/pgui-cluster-list.png b/docs/diagrams/pgui-cluster-list.png new file mode 100644 index 000000000..c815360c5 Binary files /dev/null and b/docs/diagrams/pgui-cluster-list.png differ diff --git a/docs/diagrams/pgui-cluster-startup.png b/docs/diagrams/pgui-cluster-startup.png new file mode 100644 index 000000000..7d9ebceb0 Binary files /dev/null and b/docs/diagrams/pgui-cluster-startup.png differ diff --git a/docs/diagrams/pgui-delete-cluster.png b/docs/diagrams/pgui-delete-cluster.png new file mode 100644 index 000000000..7397de0ca Binary files /dev/null and b/docs/diagrams/pgui-delete-cluster.png differ diff --git a/docs/diagrams/pgui-finished-setup.png b/docs/diagrams/pgui-finished-setup.png new file mode 100644 index 000000000..8f69b3b39 Binary files /dev/null and b/docs/diagrams/pgui-finished-setup.png differ diff --git a/docs/diagrams/pgui-new-cluster.png b/docs/diagrams/pgui-new-cluster.png new file mode 100644 index 000000000..f7e4b9529 Binary files /dev/null and b/docs/diagrams/pgui-new-cluster.png differ diff --git a/docs/diagrams/pgui-operator-logs.png b/docs/diagrams/pgui-operator-logs.png new file mode 100644 index 000000000..0608ff670 Binary files /dev/null and b/docs/diagrams/pgui-operator-logs.png differ diff --git a/docs/diagrams/pgui-waiting-for-master.png b/docs/diagrams/pgui-waiting-for-master.png new file mode 100644 index 000000000..e6ca1b6e7 Binary files /dev/null and b/docs/diagrams/pgui-waiting-for-master.png differ diff --git a/docs/operator-ui.md b/docs/operator-ui.md new file mode 100644 index 000000000..c8e2a4b8c --- /dev/null +++ b/docs/operator-ui.md @@ -0,0 +1,65 @@ +# Postgres Operator UI + +The Postgres Operator UI provides a graphical interface for a convenient +database-as-a-service user experience. Once the operator is set up by database +and/or Kubernetes (K8s) admins it's very easy for other teams to create, clone, +watch, edit and delete their own Postgres clusters. Information on the setup +and technical details can be found in the [admin docs](administrator.md#setting-up-the-postgres-operator-ui). + +## Create a new cluster + +In the top menu select the "New cluster" option and adjust the values in the +text fields. The cluster name is composed of the team plus given name. Among the +available options are [enabling load balancers](administrator.md#load-balancers-and-allowed-ip-ranges), +[volume size](user.md#increase-volume-size), +[users and databases](user.md#manifest-roles) and +[pod resources](cluster-manifest.md#postgres-container-resources). + +![pgui-new-cluster](diagrams/pgui-new-cluster.png "Create a new cluster") + +On the left side you will see a preview of the Postgres cluster manifest which +is applied when clicking on the green "Create cluster" button. + +## Cluster starting up + +After the manifest is applied to K8s the Postgres Operator will create all +necessary resources. The progress of this process can nicely be followed in UI +status page. + +![pgui-cluster-startup](diagrams/pgui-cluster-startup.png "Cluster starting up") + +![pgui-waiting-for-master](diagrams/pgui-waiting-for-master.png "Waiting for master pod") + +Usually, the startup should only take up to 1 minute. If you feel the process +got stuck click on the "Logs" button to inspect the operator logs. From the +"Status" field in the top menu you can also retrieve the logs and queue of each +worker the operator is using. The number of concurrent workers can be +[configured](reference/operator_parameters.md#general). + +![pgui-operator-logs](diagrams/pgui-operator-logs.png "Checking operator logs") + +Once the startup has finished you will see the cluster address path. When load +balancers are enabled the listed path can be used as the host name when +connecting to PostgreSQL. But, make sure your IP is within the specified +`allowedSourceRanges`. + +![pgui-finished-setup](diagrams/pgui-finished-setup.png "Status page of ready cluster") + +## Update and delete clusters + +Created clusters are listed under the menu "PostgreSQL clusters". You can get +back to cluster's status page via the "Status" button. From both menus you can +choose to edit the manifest, [clone](user.md#how-to-clone-an-existing-postgresql-cluster) +or delete a cluster. + +![pgui-cluster-list](diagrams/pgui-cluster-list.png "List of PostgreSQL clusters") + +Note, that not all [manifest options](reference/cluster_manifest.md) are yet +supported in the UI. If you try to add them in the editor view it won't have an +effect. Use `kubectl` commands instead. The displayed manifest on the left side +will also show parameters patched that way. + +When deleting a cluster you are asked to type in its namespace and name to +confirm the action. + +![pgui-delete-cluster](diagrams/pgui-delete-cluster.png "Confirm cluster deletion") diff --git a/docs/reference/command_line_and_environment.md b/docs/reference/command_line_and_environment.md index bcbe6f41d..8caf7b898 100644 --- a/docs/reference/command_line_and_environment.md +++ b/docs/reference/command_line_and_environment.md @@ -1,4 +1,4 @@ -## Command-line options +# Command-line options The following command-line options are supported for the operator: diff --git a/mkdocs.yml b/mkdocs.yml index 635f08816..eca88559f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,15 +2,14 @@ site_name: Postgres Operator repo_url: https://github.com/zalando/postgres-operator theme: readthedocs -pages: - - Introduction: index.md - - Quickstart: quickstart.md - - Administrator Guide: administrator.md - - User Guide: user.md - - Developer Guide: developer.md +nav: + - index.md + - quickstart.md + - operator-ui.md + - administrator.md + - user.md + - developer.md - Reference: - - Operator Configuration: reference/operator_parameters.md - - Cluster Manifest description: reference/cluster_manifest.md - - Command-line options and environment: reference/command_line_and_environment.md - - + - reference/operator_parameters.md + - reference/cluster_manifest.md + - reference/command_line_and_environment.md diff --git a/ui/.dockerignore b/ui/.dockerignore new file mode 100644 index 000000000..a53cb76a3 --- /dev/null +++ b/ui/.dockerignore @@ -0,0 +1,10 @@ +*# +*.pyc +*~ +.*.sw? +.git +__pycache__ + +app/node_modules +operator_ui/static/build/*.hot-update.js +operator_ui/static/build/*.hot-update.json diff --git a/ui/Dockerfile b/ui/Dockerfile new file mode 100644 index 000000000..3e1ae8756 --- /dev/null +++ b/ui/Dockerfile @@ -0,0 +1,40 @@ +FROM alpine:3.6 +MAINTAINER team-acid@zalando.de + +EXPOSE 8080 + +RUN \ + apk add --no-cache \ + alpine-sdk \ + autoconf \ + automake \ + ca-certificates \ + libffi-dev \ + libtool \ + python3 \ + python3-dev \ + zlib-dev \ + && \ + python3 -m ensurepip && \ + rm -r /usr/lib/python*/ensurepip && \ + pip3 install --upgrade \ + gevent \ + jq \ + pip \ + setuptools \ + && \ + rm -rf \ + /root/.cache \ + /tmp/* \ + /var/cache/apk/* + +COPY requirements.txt / +RUN pip3 install -r /requirements.txt + +COPY operator_ui /operator_ui + +ARG VERSION=dev +RUN sed -i "s/__version__ = .*/__version__ = '${VERSION}'/" /operator_ui/__init__.py + +WORKDIR / +ENTRYPOINT ["/usr/bin/python3", "-m", "operator_ui"] diff --git a/ui/MANIFEST.in b/ui/MANIFEST.in new file mode 100644 index 000000000..35908e432 --- /dev/null +++ b/ui/MANIFEST.in @@ -0,0 +1,3 @@ +recursive-include operator_ui/static * +recursive-include operator_ui/templates * +include *.rst diff --git a/ui/Makefile b/ui/Makefile new file mode 100644 index 000000000..f1370c7a2 --- /dev/null +++ b/ui/Makefile @@ -0,0 +1,43 @@ +.PHONY: clean test appjs docker push mock + +BINARY ?= postgres-operator-ui +BUILD_FLAGS ?= -v +CGO_ENABLED ?= 0 +ifeq ($(RACE),1) + BUILD_FLAGS += -race -a + CGO_ENABLED=1 +endif + +LOCAL_BUILD_FLAGS ?= $(BUILD_FLAGS) +LDFLAGS ?= -X=main.version=$(VERSION) +DOCKERDIR = docker + +IMAGE ?= registry.opensource.zalan.do/acid/$(BINARY) +VERSION ?= $(shell git describe --tags --always --dirty) +TAG ?= $(VERSION) +GITHEAD = $(shell git rev-parse --short HEAD) +GITURL = $(shell git config --get remote.origin.url) +GITSTATU = $(shell git status --porcelain || echo 'no changes') +TTYFLAGS = $(shell test -t 0 && echo '-it') + +default: docker + +clean: + rm -fr operator_ui/static/build + +test: + tox + +appjs: + docker run $(TTYFLAGS) -u $$(id -u) -v $$(pwd):/workdir -w /workdir/app node:10.1.0-alpine npm install + docker run $(TTYFLAGS) -u $$(id -u) -v $$(pwd):/workdir -w /workdir/app node:10.1.0-alpine npm run build + +docker: appjs + docker build --build-arg "VERSION=$(VERSION)" -t "$(IMAGE):$(TAG)" . + @echo 'Docker image $(IMAGE):$(TAG) can now be used.' + +push: docker + docker push "$(IMAGE):$(TAG)" + +mock: + docker run -it -p 8080:8080 "$(IMAGE):$(TAG)" --mock diff --git a/ui/app/.eslintignore b/ui/app/.eslintignore new file mode 100644 index 000000000..3fecb710d --- /dev/null +++ b/ui/app/.eslintignore @@ -0,0 +1 @@ +src/vendor/*.js diff --git a/ui/app/.eslintrc.yml b/ui/app/.eslintrc.yml new file mode 100644 index 000000000..b60734f2d --- /dev/null +++ b/ui/app/.eslintrc.yml @@ -0,0 +1,27 @@ +parserOptions: + sourceType: module +env: + browser: true + node: true + es6: true +extends: 'eslint:recommended' +rules: + indent: + - error + - 4 + linebreak-style: + - error + - unix + quotes: + - error + - single + prefer-const: + - error + no-redeclare: + - error + no-unused-vars: + - warn + - argsIgnorePattern: "^_" + semi: + - error + - never diff --git a/ui/app/README.rst b/ui/app/README.rst new file mode 100644 index 000000000..92af6f767 --- /dev/null +++ b/ui/app/README.rst @@ -0,0 +1,14 @@ +This directory contains the EcmaScript frontend code of the PostgreSQL Operator UI and is only needed during build time. + +The JavaScript application bundle (webpack) will be generated to ``operator_ui/static/build/app*.js`` by running: + +.. code-block:: bash + + $ npm install + $ npm run build + +Frontend development is supported by watching the source code and continuously recompiling the webpack: + +.. code-block:: bash + + $ npm start diff --git a/ui/app/package-lock.json b/ui/app/package-lock.json new file mode 100644 index 000000000..76e41dbdf --- /dev/null +++ b/ui/app/package-lock.json @@ -0,0 +1,6924 @@ +{ + "name": "postgres-operator-ui", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "requires": { + "@babel/highlight": "7.0.0" + } + }, + "@babel/core": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.4.5.tgz", + "integrity": "sha512-OvjIh6aqXtlsA8ujtGKfC7LYWksYSX8yQcM8Ay3LuvVeQ63lcOKgoZWVqcpFwkd29aYU9rVx7jxhfhiEDV9MZA==", + "requires": { + "@babel/code-frame": "7.0.0", + "@babel/generator": "7.4.4", + "@babel/helpers": "7.4.4", + "@babel/parser": "7.4.5", + "@babel/template": "7.4.4", + "@babel/traverse": "7.4.5", + "@babel/types": "7.4.4", + "convert-source-map": "1.6.0", + "debug": "4.1.1", + "json5": "2.1.0", + "lodash": "4.17.11", + "resolve": "1.11.1", + "semver": "5.7.0", + "source-map": "0.5.7" + } + }, + "@babel/generator": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz", + "integrity": "sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==", + "requires": { + "@babel/types": "7.4.4", + "jsesc": "2.5.2", + "lodash": "4.17.11", + "source-map": "0.5.7", + "trim-right": "1.0.1" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", + "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", + "dev": true, + "requires": { + "@babel/types": "7.4.4" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz", + "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "7.1.0", + "@babel/types": "7.4.4" + } + }, + "@babel/helper-call-delegate": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz", + "integrity": "sha512-l79boDFJ8S1c5hvQvG+rc+wHw6IuH7YldmRKsYtpbawsxURu/paVy57FZMomGK22/JckepaikOkY0MoAmdyOlQ==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "7.4.4", + "@babel/traverse": "7.4.5", + "@babel/types": "7.4.4" + } + }, + "@babel/helper-define-map": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.4.4.tgz", + "integrity": "sha512-IX3Ln8gLhZpSuqHJSnTNBWGDE9kdkTEWl21A/K7PQ00tseBwbqCHTvNLHSBd9M0R5rER4h5Rsvj9vw0R5SieBg==", + "dev": true, + "requires": { + "@babel/helper-function-name": "7.1.0", + "@babel/types": "7.4.4", + "lodash": "4.17.11" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", + "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==", + "dev": true, + "requires": { + "@babel/traverse": "7.4.5", + "@babel/types": "7.4.4" + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "requires": { + "@babel/helper-get-function-arity": "7.0.0", + "@babel/template": "7.4.4", + "@babel/types": "7.4.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "requires": { + "@babel/types": "7.4.4" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz", + "integrity": "sha512-VYk2/H/BnYbZDDg39hr3t2kKyifAm1W6zHRfhx8jGjIHpQEBv9dry7oQ2f3+J703TLu69nYdxsovl0XYfcnK4w==", + "dev": true, + "requires": { + "@babel/types": "7.4.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz", + "integrity": "sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg==", + "dev": true, + "requires": { + "@babel/types": "7.4.4" + } + }, + "@babel/helper-module-imports": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", + "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", + "dev": true, + "requires": { + "@babel/types": "7.4.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.4.4.tgz", + "integrity": "sha512-3Z1yp8TVQf+B4ynN7WoHPKS8EkdTbgAEy0nU0rs/1Kw4pDgmvYH3rz3aI11KgxKCba2cn7N+tqzV1mY2HMN96w==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "7.0.0", + "@babel/helper-simple-access": "7.1.0", + "@babel/helper-split-export-declaration": "7.4.4", + "@babel/template": "7.4.4", + "@babel/types": "7.4.4", + "lodash": "4.17.11" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", + "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==", + "dev": true, + "requires": { + "@babel/types": "7.4.4" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", + "dev": true + }, + "@babel/helper-regex": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.4.4.tgz", + "integrity": "sha512-Y5nuB/kESmR3tKjU8Nkn1wMGEx1tjJX076HBMeL3XLQCu6vA/YRzuTW0bbb+qRnXvQGn+d6Rx953yffl8vEy7Q==", + "dev": true, + "requires": { + "lodash": "4.17.11" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", + "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "7.0.0", + "@babel/helper-wrap-function": "7.2.0", + "@babel/template": "7.4.4", + "@babel/traverse": "7.4.5", + "@babel/types": "7.4.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.4.4.tgz", + "integrity": "sha512-04xGEnd+s01nY1l15EuMS1rfKktNF+1CkKmHoErDppjAAZL+IUBZpzT748x262HF7fibaQPhbvWUl5HeSt1EXg==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "7.0.0", + "@babel/helper-optimise-call-expression": "7.0.0", + "@babel/traverse": "7.4.5", + "@babel/types": "7.4.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", + "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==", + "dev": true, + "requires": { + "@babel/template": "7.4.4", + "@babel/types": "7.4.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", + "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "requires": { + "@babel/types": "7.4.4" + } + }, + "@babel/helper-wrap-function": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", + "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==", + "dev": true, + "requires": { + "@babel/helper-function-name": "7.1.0", + "@babel/template": "7.4.4", + "@babel/traverse": "7.4.5", + "@babel/types": "7.4.4" + } + }, + "@babel/helpers": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.4.4.tgz", + "integrity": "sha512-igczbR/0SeuPR8RFfC7tGrbdTbFL3QTvH6D+Z6zNxnTe//GyqmtHmDkzrqDmyZ3eSwPqB/LhyKoU5DXsp+Vp2A==", + "requires": { + "@babel/template": "7.4.4", + "@babel/traverse": "7.4.5", + "@babel/types": "7.4.4" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "requires": { + "chalk": "2.4.2", + "esutils": "2.0.2", + "js-tokens": "4.0.0" + } + }, + "@babel/parser": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.5.tgz", + "integrity": "sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew==" + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz", + "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0", + "@babel/helper-remap-async-to-generator": "7.1.0", + "@babel/plugin-syntax-async-generators": "7.2.0" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", + "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0", + "@babel/plugin-syntax-json-strings": "7.2.0" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.4.4.tgz", + "integrity": "sha512-dMBG6cSPBbHeEBdFXeQ2QLc5gUpg4Vkaz8octD4aoW/ISO+jBOcsuxYL7bsb5WSu8RLP6boxrBIALEHgoHtO9g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0", + "@babel/plugin-syntax-object-rest-spread": "7.2.0" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0", + "@babel/plugin-syntax-optional-catch-binding": "7.2.0" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz", + "integrity": "sha512-j1NwnOqMG9mFUOH58JTFsA/+ZYzQLUZ/drqWUqxCYLGeu2JFZL8YrNC9hBxKmWtAuOCHPcRpgv7fhap09Fb4kA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0", + "@babel/helper-regex": "7.4.4", + "regexpu-core": "4.5.4" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz", + "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz", + "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", + "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", + "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.4.4.tgz", + "integrity": "sha512-YiqW2Li8TXmzgbXw+STsSqPBPFnGviiaSp6CYOq55X8GQ2SGVLrXB6pNid8HkqkZAzOH6knbai3snhP7v0fNwA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "7.0.0", + "@babel/helper-plugin-utils": "7.0.0", + "@babel/helper-remap-async-to-generator": "7.1.0" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz", + "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.4.4.tgz", + "integrity": "sha512-jkTUyWZcTrwxu5DD4rWz6rDB5Cjdmgz6z7M7RLXOJyCUkFBawssDGcGh8M/0FTSB87avyJI1HsTwUXp9nKA1PA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0", + "lodash": "4.17.11" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.4.tgz", + "integrity": "sha512-/e44eFLImEGIpL9qPxSRat13I5QNRgBLu2hOQJCF7VLy/otSM/sypV1+XaIw5+502RX/+6YaSAPmldk+nhHDPw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "7.0.0", + "@babel/helper-define-map": "7.4.4", + "@babel/helper-function-name": "7.1.0", + "@babel/helper-optimise-call-expression": "7.0.0", + "@babel/helper-plugin-utils": "7.0.0", + "@babel/helper-replace-supers": "7.4.4", + "@babel/helper-split-export-declaration": "7.4.4", + "globals": "11.12.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz", + "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.4.4.tgz", + "integrity": "sha512-/aOx+nW0w8eHiEHm+BTERB2oJn5D127iye/SUQl7NjHy0lf+j7h4MKMMSOwdazGq9OxgiNADncE+SRJkCxjZpQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.4.tgz", + "integrity": "sha512-P05YEhRc2h53lZDjRPk/OektxCVevFzZs2Gfjd545Wde3k+yFDbXORgl2e0xpbq8mLcKJ7Idss4fAg0zORN/zg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0", + "@babel/helper-regex": "7.4.4", + "regexpu-core": "4.5.4" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.2.0.tgz", + "integrity": "sha512-q+yuxW4DsTjNceUiTzK0L+AfQ0zD9rWaTLiUqHA8p0gxx7lu1EylenfzjeIWNkPy6e/0VG/Wjw9uf9LueQwLOw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz", + "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "7.1.0", + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz", + "integrity": "sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.4.tgz", + "integrity": "sha512-iU9pv7U+2jC9ANQkKeNF6DrPy4GBa4NWQtl6dHB4Pb3izX2JOEvDTFarlNsBj/63ZEzNNIAMs3Qw4fNCcSOXJA==", + "dev": true, + "requires": { + "@babel/helper-function-name": "7.1.0", + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz", + "integrity": "sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz", + "integrity": "sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.2.0.tgz", + "integrity": "sha512-mK2A8ucqz1qhrdqjS9VMIDfIvvT2thrEsIQzbaTdc5QFzhDjQv2CkJJ5f6BXIkgbmaoax3zBr2RyvV/8zeoUZw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "7.4.4", + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.4.4.tgz", + "integrity": "sha512-4sfBOJt58sEo9a2BQXnZq+Q3ZTSAUXyK3E30o36BOGnJ+tvJ6YSxF0PG6kERvbeISgProodWuI9UVG3/FMY6iw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "7.4.4", + "@babel/helper-plugin-utils": "7.0.0", + "@babel/helper-simple-access": "7.1.0" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.4.4.tgz", + "integrity": "sha512-MSiModfILQc3/oqnG7NrP1jHaSPryO6tA2kOMmAQApz5dayPxWiHqmq4sWH2xF5LcQK56LlbKByCd8Aah/OIkQ==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "7.4.4", + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz", + "integrity": "sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "7.4.4", + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.5.tgz", + "integrity": "sha512-z7+2IsWafTBbjNsOxU/Iv5CvTJlr5w4+HGu1HovKYTtgJ362f7kBcQglkfmlspKKZ3bgrbSGvLfNx++ZJgCWsg==", + "dev": true, + "requires": { + "regexp-tree": "0.1.10" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz", + "integrity": "sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz", + "integrity": "sha512-VMyhPYZISFZAqAPVkiYb7dUe2AsVi2/wCT5+wZdsNO31FojQJa9ns40hzZ6U9f50Jlq4w6qwzdBB2uwqZ00ebg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0", + "@babel/helper-replace-supers": "7.4.4" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz", + "integrity": "sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw==", + "dev": true, + "requires": { + "@babel/helper-call-delegate": "7.4.4", + "@babel/helper-get-function-arity": "7.0.0", + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz", + "integrity": "sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.5.tgz", + "integrity": "sha512-gBKRh5qAaCWntnd09S8QC7r3auLCqq5DI6O0DlfoyDjslSBVqBibrMdsqO+Uhmx3+BlOmE/Kw1HFxmGbv0N9dA==", + "dev": true, + "requires": { + "regenerator-transform": "0.14.0" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz", + "integrity": "sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.4.4.tgz", + "integrity": "sha512-aMVojEjPszvau3NRg+TIH14ynZLvPewH4xhlCW1w6A3rkxTS1m4uwzRclYR9oS+rl/dr+kT+pzbfHuAWP/lc7Q==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "7.0.0", + "@babel/helper-plugin-utils": "7.0.0", + "resolve": "1.11.1", + "semver": "5.7.0" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", + "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz", + "integrity": "sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz", + "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0", + "@babel/helper-regex": "7.4.4" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz", + "integrity": "sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "7.0.0", + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz", + "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.4.tgz", + "integrity": "sha512-il+/XdNw01i93+M9J9u4T7/e/Ue/vWfNZE4IRUQjplu2Mqb/AFTDimkw2tdEdSH50wuQXZAbXSql0UphQke+vA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0", + "@babel/helper-regex": "7.4.4", + "regexpu-core": "4.5.4" + } + }, + "@babel/polyfill": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.4.4.tgz", + "integrity": "sha512-WlthFLfhQQhh+A2Gn5NSFl0Huxz36x86Jn+E9OW7ibK8edKPq+KLy4apM1yDpQ8kJOVi1OVjpP4vSDLdrI04dg==", + "requires": { + "core-js": "2.6.9", + "regenerator-runtime": "0.13.2" + } + }, + "@babel/preset-env": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.4.5.tgz", + "integrity": "sha512-f2yNVXM+FsR5V8UwcFeIHzHWgnhXg3NpRmy0ADvALpnhB0SLbCvrCRr4BLOUYbQNLS+Z0Yer46x9dJXpXewI7w==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "7.0.0", + "@babel/helper-plugin-utils": "7.0.0", + "@babel/plugin-proposal-async-generator-functions": "7.2.0", + "@babel/plugin-proposal-json-strings": "7.2.0", + "@babel/plugin-proposal-object-rest-spread": "7.4.4", + "@babel/plugin-proposal-optional-catch-binding": "7.2.0", + "@babel/plugin-proposal-unicode-property-regex": "7.4.4", + "@babel/plugin-syntax-async-generators": "7.2.0", + "@babel/plugin-syntax-json-strings": "7.2.0", + "@babel/plugin-syntax-object-rest-spread": "7.2.0", + "@babel/plugin-syntax-optional-catch-binding": "7.2.0", + "@babel/plugin-transform-arrow-functions": "7.2.0", + "@babel/plugin-transform-async-to-generator": "7.4.4", + "@babel/plugin-transform-block-scoped-functions": "7.2.0", + "@babel/plugin-transform-block-scoping": "7.4.4", + "@babel/plugin-transform-classes": "7.4.4", + "@babel/plugin-transform-computed-properties": "7.2.0", + "@babel/plugin-transform-destructuring": "7.4.4", + "@babel/plugin-transform-dotall-regex": "7.4.4", + "@babel/plugin-transform-duplicate-keys": "7.2.0", + "@babel/plugin-transform-exponentiation-operator": "7.2.0", + "@babel/plugin-transform-for-of": "7.4.4", + "@babel/plugin-transform-function-name": "7.4.4", + "@babel/plugin-transform-literals": "7.2.0", + "@babel/plugin-transform-member-expression-literals": "7.2.0", + "@babel/plugin-transform-modules-amd": "7.2.0", + "@babel/plugin-transform-modules-commonjs": "7.4.4", + "@babel/plugin-transform-modules-systemjs": "7.4.4", + "@babel/plugin-transform-modules-umd": "7.2.0", + "@babel/plugin-transform-named-capturing-groups-regex": "7.4.5", + "@babel/plugin-transform-new-target": "7.4.4", + "@babel/plugin-transform-object-super": "7.2.0", + "@babel/plugin-transform-parameters": "7.4.4", + "@babel/plugin-transform-property-literals": "7.2.0", + "@babel/plugin-transform-regenerator": "7.4.5", + "@babel/plugin-transform-reserved-words": "7.2.0", + "@babel/plugin-transform-shorthand-properties": "7.2.0", + "@babel/plugin-transform-spread": "7.2.2", + "@babel/plugin-transform-sticky-regex": "7.2.0", + "@babel/plugin-transform-template-literals": "7.4.4", + "@babel/plugin-transform-typeof-symbol": "7.2.0", + "@babel/plugin-transform-unicode-regex": "7.4.4", + "@babel/types": "7.4.4", + "browserslist": "4.6.3", + "core-js-compat": "3.1.4", + "invariant": "2.2.4", + "js-levenshtein": "1.1.6", + "semver": "5.7.0" + } + }, + "@babel/runtime": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz", + "integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==", + "requires": { + "regenerator-runtime": "0.13.2" + } + }, + "@babel/template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz", + "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==", + "requires": { + "@babel/code-frame": "7.0.0", + "@babel/parser": "7.4.5", + "@babel/types": "7.4.4" + } + }, + "@babel/traverse": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.5.tgz", + "integrity": "sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A==", + "requires": { + "@babel/code-frame": "7.0.0", + "@babel/generator": "7.4.4", + "@babel/helper-function-name": "7.1.0", + "@babel/helper-split-export-declaration": "7.4.4", + "@babel/parser": "7.4.5", + "@babel/types": "7.4.4", + "debug": "4.1.1", + "globals": "11.12.0", + "lodash": "4.17.11" + } + }, + "@babel/types": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz", + "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==", + "requires": { + "esutils": "2.0.2", + "lodash": "4.17.11", + "to-fast-properties": "2.0.0" + } + }, + "@types/acorn": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.5.tgz", + "integrity": "sha512-603sPiZ4GVRHPvn6vNgEAvJewKsy+zwRWYS2MeIMemgoAtcjlw2G3lALxrb9OPA17J28bkB71R33yXlQbUatCA==", + "dev": true, + "requires": { + "@types/estree": "0.0.39" + } + }, + "@types/babel-types": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.7.tgz", + "integrity": "sha512-dBtBbrc+qTHy1WdfHYjBwRln4+LWqASWakLHsWHR2NWHIFkv4W3O070IGoGLEBrJBvct3r0L1BUPuvURi7kYUQ==", + "dev": true + }, + "@types/babylon": { + "version": "6.16.5", + "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.5.tgz", + "integrity": "sha512-xH2e58elpj1X4ynnKp9qSnWlsRTIs6n3tgLGNfwAGHwePw0mulHQllV34n0T25uYSu1k0hRKkWXF890B1yS47w==", + "dev": true, + "requires": { + "@types/babel-types": "7.0.7" + } + }, + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "@webassemblyjs/ast": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", + "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==", + "dev": true, + "requires": { + "@webassemblyjs/helper-module-context": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/wast-parser": "1.8.5" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz", + "integrity": "sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz", + "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz", + "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==", + "dev": true + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz", + "integrity": "sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==", + "dev": true, + "requires": { + "@webassemblyjs/wast-printer": "1.8.5" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz", + "integrity": "sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==", + "dev": true + }, + "@webassemblyjs/helper-module-context": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz", + "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "mamacro": "0.0.3" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz", + "integrity": "sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz", + "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-buffer": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/wasm-gen": "1.8.5" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz", + "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==", + "dev": true, + "requires": { + "@xtuc/ieee754": "1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.8.5.tgz", + "integrity": "sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.8.5.tgz", + "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz", + "integrity": "sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-buffer": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/helper-wasm-section": "1.8.5", + "@webassemblyjs/wasm-gen": "1.8.5", + "@webassemblyjs/wasm-opt": "1.8.5", + "@webassemblyjs/wasm-parser": "1.8.5", + "@webassemblyjs/wast-printer": "1.8.5" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz", + "integrity": "sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/ieee754": "1.8.5", + "@webassemblyjs/leb128": "1.8.5", + "@webassemblyjs/utf8": "1.8.5" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz", + "integrity": "sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-buffer": "1.8.5", + "@webassemblyjs/wasm-gen": "1.8.5", + "@webassemblyjs/wasm-parser": "1.8.5" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz", + "integrity": "sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-api-error": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/ieee754": "1.8.5", + "@webassemblyjs/leb128": "1.8.5", + "@webassemblyjs/utf8": "1.8.5" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz", + "integrity": "sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/floating-point-hex-parser": "1.8.5", + "@webassemblyjs/helper-api-error": "1.8.5", + "@webassemblyjs/helper-code-frame": "1.8.5", + "@webassemblyjs/helper-fsm": "1.8.5", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz", + "integrity": "sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/wast-parser": "1.8.5", + "@xtuc/long": "4.2.2" + } + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "acorn-dynamic-import": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz", + "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==", + "dev": true, + "requires": { + "acorn": "5.7.3" + } + }, + "acorn-globals": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz", + "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=", + "dev": true, + "requires": { + "acorn": "4.0.13" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "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.3.0" + }, + "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 + } + } + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "dev": true + }, + "ajv-keywords": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "dev": true + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, + "requires": { + "kind-of": "3.2.2", + "longest": "1.0.1", + "repeat-string": "1.6.1" + } + }, + "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": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "1.9.3" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "3.1.10", + "normalize-path": "2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "1.1.0" + } + } + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "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.3" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "inherits": "2.0.4", + "minimalistic-assert": "1.0.1" + } + }, + "assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "dev": true, + "requires": { + "object-assign": "4.1.1", + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + }, + "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.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "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 + } + } + }, + "babel-loader": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz", + "integrity": "sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw==", + "dev": true, + "requires": { + "find-cache-dir": "2.1.0", + "loader-utils": "1.2.3", + "mkdirp": "0.5.1", + "pify": "4.0.1" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + } + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "esutils": "2.0.2", + "lodash": "4.17.11", + "to-fast-properties": "1.0.3" + }, + "dependencies": { + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + } + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "1.0.1", + "class-utils": "0.3.6", + "component-emitter": "1.3.0", + "define-property": "1.0.0", + "isobject": "3.0.1", + "mixin-deep": "1.3.2", + "pascalcase": "0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "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==", + "dev": true + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true + }, + "bit-twiddle": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bit-twiddle/-/bit-twiddle-1.0.2.tgz", + "integrity": "sha1-DGwfq+KyPRcXPZpht7cJPrnhdp4=" + }, + "bluebird": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", + "dev": true + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "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" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.3", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "brfs": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/brfs/-/brfs-1.6.1.tgz", + "integrity": "sha512-OfZpABRQQf+Xsmju8XE9bDjs+uU4vLREGolP7bDgcpsI17QREyZ4Bl+2KLxxx1kCgA0fAIhKQBaBYh+PEcCqYQ==", + "dev": true, + "requires": { + "quote-stream": "1.0.2", + "resolve": "1.11.1", + "static-module": "2.2.5", + "through2": "2.0.5" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "1.0.3", + "cipher-base": "1.0.4", + "create-hash": "1.2.0", + "evp_bytestokey": "1.0.3", + "inherits": "2.0.4", + "safe-buffer": "5.1.2" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "1.2.0", + "browserify-des": "1.0.2", + "evp_bytestokey": "1.0.3" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "1.0.4", + "des.js": "1.0.0", + "inherits": "2.0.4", + "safe-buffer": "5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "randombytes": "2.1.0" + } + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "browserify-rsa": "4.0.1", + "create-hash": "1.2.0", + "create-hmac": "1.1.7", + "elliptic": "6.5.0", + "inherits": "2.0.4", + "parse-asn1": "5.1.4" + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "1.0.10" + } + }, + "browserslist": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.3.tgz", + "integrity": "sha512-CNBqTCq22RKM8wKJNowcqihHJ4SkI8CGeK7KOR9tPboXUuS5Zk5lQgzzTbs4oxD8x+6HUshZUa2OyNI9lR93bQ==", + "dev": true, + "requires": { + "caniuse-lite": "1.0.30000979", + "electron-to-chromium": "1.3.180", + "node-releases": "1.1.24" + } + }, + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "dev": true, + "requires": { + "base64-js": "1.3.0", + "ieee754": "1.1.13", + "isarray": "1.0.0" + } + }, + "buffer-equal": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "cacache": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.3.tgz", + "integrity": "sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==", + "dev": true, + "requires": { + "bluebird": "3.5.5", + "chownr": "1.1.1", + "figgy-pudding": "3.5.1", + "glob": "7.1.4", + "graceful-fs": "4.2.0", + "lru-cache": "5.1.1", + "mississippi": "3.0.0", + "mkdirp": "0.5.1", + "move-concurrently": "1.0.1", + "promise-inflight": "1.0.1", + "rimraf": "2.6.3", + "ssri": "6.0.1", + "unique-filename": "1.1.1", + "y18n": "4.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "3.0.3" + } + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "1.0.0", + "component-emitter": "1.3.0", + "get-value": "2.0.6", + "has-value": "1.0.0", + "isobject": "3.0.1", + "set-value": "2.0.1", + "to-object-path": "0.3.0", + "union-value": "1.0.1", + "unset-value": "1.0.0" + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30000979", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000979.tgz", + "integrity": "sha512-gcu45yfq3B7Y+WB05fOMfr0EiSlq+1u+m6rPHyJli/Wy3PVQNGaU7VA4bZE5qw+AU2UVOBR/N5g1bzADUqdvFw==", + "dev": true + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "requires": { + "align-text": "0.1.4", + "lazy-cache": "1.0.4" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" + } + }, + "character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=", + "dev": true, + "requires": { + "is-regex": "1.0.4" + } + }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, + "chokidar": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", + "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", + "dev": true, + "requires": { + "anymatch": "2.0.0", + "async-each": "1.0.3", + "braces": "2.3.2", + "fsevents": "1.2.9", + "glob-parent": "3.1.0", + "inherits": "2.0.4", + "is-binary-path": "1.0.1", + "is-glob": "4.0.1", + "normalize-path": "3.0.0", + "path-is-absolute": "1.0.1", + "readdirp": "2.2.1", + "upath": "1.1.2" + } + }, + "chownr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "dev": true + }, + "chrome-trace-event": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", + "dev": true, + "requires": { + "tslib": "1.10.0" + } + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "2.0.4", + "safe-buffer": "5.1.2" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "3.1.0", + "define-property": "0.2.5", + "isobject": "3.0.1", + "static-extend": "0.1.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + } + } + }, + "clean-css": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "dev": true, + "requires": { + "source-map": "0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "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" + } + }, + "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": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "requires": { + "center-align": "0.1.3", + "right-align": "0.1.3", + "wordwrap": "0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true + } + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "1.0.0", + "object-visit": "1.0.1" + } + }, + "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==", + "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=" + }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "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 + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "1.1.1", + "inherits": "2.0.4", + "readable-stream": "2.3.6", + "typedarray": "0.0.6" + } + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "0.1.4" + } + }, + "constantinople": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.1.2.tgz", + "integrity": "sha512-yePcBqEFhLOqSBtwYOGGS1exHo/s1xjekXiinh4itpNQGCu4KA1euPh1fg07N2wMITZXQkBz75Ntdt1ctGZouw==", + "dev": true, + "requires": { + "@types/babel-types": "7.0.7", + "@types/babylon": "6.16.5", + "babel-types": "6.26.0", + "babylon": "6.18.0" + } + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "dev": true, + "requires": { + "aproba": "1.2.0", + "fs-write-stream-atomic": "1.0.10", + "iferr": "0.1.5", + "mkdirp": "0.5.1", + "rimraf": "2.6.3", + "run-queue": "1.0.3" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-js": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", + "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==" + }, + "core-js-compat": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.1.4.tgz", + "integrity": "sha512-Z5zbO9f1d0YrJdoaQhphVAnKPimX92D6z8lCGphH89MNRxlL1prI9ExJPqVwP0/kgkQCv8c4GJGT8X16yUncOg==", + "dev": true, + "requires": { + "browserslist": "4.6.3", + "core-js-pure": "3.1.4", + "semver": "6.1.3" + }, + "dependencies": { + "semver": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.3.tgz", + "integrity": "sha512-aymF+56WJJMyXQHcd4hlK4N75rwj5RQpfW8ePlQnJsTYOBLlLbcIErR/G1s9SkIvKBqOudR3KAx4wEqP+F1hNQ==", + "dev": true + } + } + }, + "core-js-pure": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.1.4.tgz", + "integrity": "sha512-uJ4Z7iPNwiu1foygbcZYJsJs1jiXrTTCvxfLDXNhI/I+NHbSIEyr548y4fcsCEyWY0XgfAG/qqaunJ1SThHenA==", + "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=", + "dev": true + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "elliptic": "6.5.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "1.0.4", + "inherits": "2.0.4", + "md5.js": "1.3.5", + "ripemd160": "2.0.2", + "sha.js": "2.4.11" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "1.0.4", + "create-hash": "1.2.0", + "inherits": "2.0.4", + "ripemd160": "2.0.2", + "safe-buffer": "5.1.2", + "sha.js": "2.4.11" + } + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "4.1.5", + "shebang-command": "1.2.0", + "which": "1.3.1" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "1.0.1", + "browserify-sign": "4.0.4", + "create-ecdh": "4.0.3", + "create-hash": "1.2.0", + "create-hmac": "1.1.7", + "diffie-hellman": "5.0.3", + "inherits": "2.0.4", + "pbkdf2": "3.0.17", + "public-encrypt": "4.0.3", + "randombytes": "2.1.0", + "randomfill": "1.0.4" + } + }, + "cyclist": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", + "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", + "dev": true + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "date-time": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/date-time/-/date-time-2.1.0.tgz", + "integrity": "sha512-/9+C44X7lot0IeiyfgJmETtRMhBidBYM2QFFIkGa0U1k+hSyY87Nw7PY3eDqpvCBm7I3WCSfPeZskW/YYq6m4g==", + "dev": true, + "requires": { + "time-zone": "1.0.0" + } + }, + "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.2" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "dedent-js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dedent-js/-/dedent-js-1.0.1.tgz", + "integrity": "sha1-vuX7fJ5yfYXf+iRZDRDsGrElUwU=", + "dev": true + }, + "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-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "1.0.2", + "isobject": "3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "des.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "dev": true, + "requires": { + "inherits": "2.0.4", + "minimalistic-assert": "1.0.1" + } + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "miller-rabin": "4.0.1", + "randombytes": "2.1.0" + } + }, + "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" + } + }, + "doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=", + "dev": true + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true + }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dev": true, + "requires": { + "readable-stream": "2.3.6" + } + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "requires": { + "end-of-stream": "1.4.1", + "inherits": "2.0.4", + "readable-stream": "2.3.6", + "stream-shift": "1.0.0" + } + }, + "earcut": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.1.5.tgz", + "integrity": "sha512-QFWC7ywTVLtvRAJTVp8ugsuuGQ5mVqNmJ1cRYeLrSHgP3nycr2RHTJob9OtM0v8ujuoKN0NY1a93J/omeTL1PA==" + }, + "electron-to-chromium": { + "version": "1.3.180", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.180.tgz", + "integrity": "sha512-jwI82/63GeH7f08IR+4v/tbGM4DMAApMZO0SXLcC0np4lcqWjQBl0MIHkfXEqesLc55+NhVVX8g7eFlamEWoNQ==", + "dev": true + }, + "elliptic": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.0.tgz", + "integrity": "sha512-eFOJTMyCYb7xtE/caJ6JJu+bhi67WCYNbkGSknu20pmM8Ke/bqOfdnZWxyoGN26JgfxTbXrsCkEw4KheCT/KGg==", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "brorand": "1.1.0", + "hash.js": "1.1.7", + "hmac-drbg": "1.0.1", + "inherits": "2.0.4", + "minimalistic-assert": "1.0.1", + "minimalistic-crypto-utils": "1.0.1" + } + }, + "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 + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "once": "1.4.0" + } + }, + "enhanced-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", + "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", + "dev": true, + "requires": { + "graceful-fs": "4.2.0", + "memory-fs": "0.4.1", + "tapable": "1.1.3" + } + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, + "requires": { + "prr": "1.0.1" + } + }, + "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=" + }, + "escodegen": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.1.tgz", + "integrity": "sha512-6hTjO1NAWkHnDk3OqQ4YrCuwwmGHL9S3nPlzBOUG/R44rda3wLNrfvQ5fkSGjyhHFKM7ALPKcKGrwvCLe0lC7Q==", + "dev": true, + "requires": { + "esprima": "3.1.3", + "estraverse": "4.2.0", + "esutils": "2.0.2", + "optionator": "0.8.2", + "source-map": "0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, + "eslint": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", + "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", + "dev": true, + "requires": { + "ajv": "5.5.2", + "babel-code-frame": "6.26.0", + "chalk": "2.4.2", + "concat-stream": "1.6.2", + "cross-spawn": "5.1.0", + "debug": "3.2.6", + "doctrine": "2.1.0", + "eslint-scope": "3.7.3", + "eslint-visitor-keys": "1.0.0", + "espree": "3.5.4", + "esquery": "1.0.1", + "esutils": "2.0.2", + "file-entry-cache": "2.0.0", + "functional-red-black-tree": "1.0.1", + "glob": "7.1.4", + "globals": "11.12.0", + "ignore": "3.3.10", + "imurmurhash": "0.1.4", + "inquirer": "3.3.0", + "is-resolvable": "1.1.0", + "js-yaml": "3.13.1", + "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", + "pluralize": "7.0.0", + "progress": "2.0.3", + "regexpp": "1.1.0", + "require-uncached": "1.0.3", + "semver": "5.7.0", + "strip-ansi": "4.0.0", + "strip-json-comments": "2.0.1", + "table": "4.0.2", + "text-table": "0.2.0" + }, + "dependencies": { + "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.2" + } + } + } + }, + "eslint-config-riot": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-riot/-/eslint-config-riot-1.0.0.tgz", + "integrity": "sha1-+9ZThpgLMPvNDhMF1MP7hhTvIRk=", + "dev": true + }, + "eslint-loader": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-1.9.0.tgz", + "integrity": "sha512-40aN976qSNPyb9ejTqjEthZITpls1SVKtwguahmH1dzGCwQU/vySE+xX33VZmD8csU0ahVNCtFlsPgKqRBiqgg==", + "dev": true, + "requires": { + "loader-fs-cache": "1.0.2", + "loader-utils": "1.2.3", + "object-assign": "4.1.1", + "object-hash": "1.3.1", + "rimraf": "2.6.3" + } + }, + "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.2.1", + "estraverse": "4.2.0" + } + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, + "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.7.3", + "acorn-jsx": "3.0.1" + } + }, + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "4.2.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.2.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" + }, + "eventemitter3": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz", + "integrity": "sha1-teEHm1n7XhuidxwKmTvgYKWMmbo=" + }, + "events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", + "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==", + "dev": true + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "1.3.5", + "safe-buffer": "5.1.2" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "6.0.5", + "get-stream": "4.1.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" + }, + "dependencies": { + "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.5", + "path-key": "2.0.1", + "semver": "5.7.0", + "shebang-command": "1.2.0", + "which": "1.3.1" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "requires": { + "homedir-polyfill": "1.0.3" + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "2.0.4" + } + } + } + }, + "external-editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "dev": true, + "requires": { + "chardet": "0.4.2", + "iconv-lite": "0.4.24", + "tmp": "0.0.33" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "falafel": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/falafel/-/falafel-2.1.0.tgz", + "integrity": "sha1-lrsXdh2rqU9G0AFzizzt86Z/4Gw=", + "dev": true, + "requires": { + "acorn": "5.7.3", + "foreach": "2.0.5", + "isarray": "0.0.1", + "object-keys": "1.1.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, + "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=", + "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=", + "dev": true + }, + "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 + }, + "figgy-pudding": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", + "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", + "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" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "1.3.4", + "object-assign": "4.1.1" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "1.0.1", + "make-dir": "2.1.0", + "pkg-dir": "3.0.0" + } + }, + "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" + } + }, + "findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dev": true, + "requires": { + "detect-file": "1.0.0", + "is-glob": "4.0.1", + "micromatch": "3.1.10", + "resolve-dir": "1.0.1" + } + }, + "flat-cache": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "dev": true, + "requires": { + "circular-json": "0.3.3", + "graceful-fs": "4.2.0", + "rimraf": "2.6.3", + "write": "0.2.1" + } + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "requires": { + "inherits": "2.0.4", + "readable-stream": "2.3.6" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "0.2.2" + } + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "2.0.4", + "readable-stream": "2.3.6" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, + "requires": { + "graceful-fs": "4.2.0", + "iferr": "0.1.5", + "imurmurhash": "0.1.4", + "readable-stream": "2.3.6" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "dev": true, + "optional": true, + "requires": { + "nan": "2.14.0", + "node-pre-gyp": "0.12.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.3.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "2.3.5" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": 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.3" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "optional": 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" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "2.1.2" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "5.1.2", + "yallist": "3.0.3" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "2.3.5" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "4.1.1", + "iconv-lite": "0.4.24", + "sax": "1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "1.0.3", + "mkdirp": "0.5.1", + "needle": "2.3.0", + "nopt": "4.0.1", + "npm-packlist": "1.4.1", + "npmlog": "4.1.2", + "rc": "1.2.8", + "rimraf": "2.6.3", + "semver": "5.7.0", + "tar": "4.4.8" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1.1.1", + "osenv": "0.1.5" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "3.0.1", + "npm-bundled": "1.0.6" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "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", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "optional": true, + "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", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "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" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.7.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "1.1.1", + "fs-minipass": "1.2.5", + "minipass": "2.3.5", + "minizlib": "1.2.1", + "mkdirp": "0.5.1", + "safe-buffer": "5.1.2", + "yallist": "3.0.3" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true + } + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "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-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" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.4", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "3.1.0", + "path-dirname": "1.0.2" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "2.1.1" + } + } + } + }, + "global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "requires": { + "global-prefix": "3.0.0" + }, + "dependencies": { + "global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "requires": { + "ini": "1.3.5", + "kind-of": "6.0.2", + "which": "1.3.1" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "requires": { + "expand-tilde": "2.0.2", + "homedir-polyfill": "1.0.3", + "ini": "1.3.5", + "is-windows": "1.0.2", + "which": "1.3.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==" + }, + "graceful-fs": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz", + "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==", + "dev": true + }, + "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.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "2.0.6", + "has-values": "1.0.0", + "isobject": "3.0.1" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dev": true, + "requires": { + "inherits": "2.0.4", + "safe-buffer": "5.1.2" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "2.0.4", + "minimalistic-assert": "1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "1.1.7", + "minimalistic-assert": "1.0.1", + "minimalistic-crypto-utils": "1.0.1" + } + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "requires": { + "parse-passwd": "1.0.0" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "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" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "requires": { + "pkg-dir": "3.0.0", + "resolve-cwd": "2.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "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.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "dev": true, + "requires": { + "ansi-escapes": "3.2.0", + "chalk": "2.4.2", + "cli-cursor": "2.1.0", + "cli-width": "2.2.0", + "external-editor": "2.2.0", + "figures": "2.0.0", + "lodash": "4.17.11", + "mute-stream": "0.0.7", + "run-async": "2.3.0", + "rx-lite": "4.0.8", + "rx-lite-aggregates": "4.0.8", + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "through": "2.3.8" + } + }, + "interpret": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "1.4.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "1.13.1" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-expression": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-3.0.0.tgz", + "integrity": "sha1-Oayqa+f9HzRx3ELHQW5hwkMXrJ8=", + "dev": true, + "requires": { + "acorn": "4.0.13", + "object-assign": "4.1.1" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "dev": true + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "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": "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 + }, + "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-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "3.0.1" + } + }, + "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-reference": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.2.tgz", + "integrity": "sha512-Kn5g8c7XHKejFOpTf2QN9YjiHHKl5xRj+2uAZf9iM2//nkBNi/NNeB5JMoun28nEaUVHyPUzqzhfRlfAirEjXg==", + "dev": true, + "requires": { + "@types/estree": "0.0.39" + } + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "1.0.3" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "ismobilejs": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/ismobilejs/-/ismobilejs-0.5.2.tgz", + "integrity": "sha512-ta9UdV60xVZk/ZafFtSFslQaE76SvNkcs1r73d2PVR21zVzx9xuYv9tNe4MxA1NN7WoeCc2RjGot3Bz1eHDx3Q==" + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true + }, + "js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "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.10", + "esprima": "4.0.1" + }, + "dependencies": { + "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 + } + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "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-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "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 + }, + "json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "requires": { + "minimist": "1.2.0" + } + }, + "jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=", + "dev": true, + "requires": { + "is-promise": "2.1.0", + "promise": "7.3.1" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "2.0.0" + } + }, + "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" + } + }, + "loader-fs-cache": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/loader-fs-cache/-/loader-fs-cache-1.0.2.tgz", + "integrity": "sha512-70IzT/0/L+M20jUlEqZhZyArTU6VKLRTYRDAYN26g4jfzpJqjipLL3/hgYpySqI9PwsVRHHFja0LfEmsx9X2Cw==", + "dev": true, + "requires": { + "find-cache-dir": "0.1.1", + "mkdirp": "0.5.1" + }, + "dependencies": { + "find-cache-dir": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz", + "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=", + "dev": true, + "requires": { + "commondir": "1.0.1", + "mkdirp": "0.5.1", + "pkg-dir": "1.0.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "pkg-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", + "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "dev": true, + "requires": { + "find-up": "1.1.2" + } + } + } + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "dev": true, + "requires": { + "big.js": "5.2.2", + "emojis-list": "2.1.0", + "json5": "1.0.1" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "1.2.0" + } + } + } + }, + "locate-character": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-2.0.5.tgz", + "integrity": "sha512-n2GmejDXtOPBAZdIiEFy5dJ5N38xBCXLNOtw2WpB9kGh6pnrEuKlwYI+Tkpofc4wDtVXHtoAOJaMRlYG/oYaxg==", + "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" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "lodash.every": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.every/-/lodash.every-4.6.0.tgz", + "integrity": "sha1-64mYS+vENkJ5uzrvu9HKGb+mxqc=", + "dev": true + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "lodash.foreach": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", + "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=", + "dev": true + }, + "lodash.map": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=", + "dev": true + }, + "lodash.maxby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.maxby/-/lodash.maxby-4.6.0.tgz", + "integrity": "sha1-CCJABo88eiJ6oAqDgOTzjPB4bj0=", + "dev": true + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "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": "4.0.0" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, + "magic-string": { + "version": "0.22.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz", + "integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==", + "dev": true, + "requires": { + "vlq": "0.2.3" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "4.0.1", + "semver": "5.7.0" + } + }, + "mamacro": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", + "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==", + "dev": true + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "1.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "1.0.1" + } + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "3.0.4", + "inherits": "2.0.4", + "safe-buffer": "5.1.2" + } + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "requires": { + "map-age-cleaner": "0.1.3", + "mimic-fn": "2.1.0", + "p-is-promise": "2.1.0" + }, + "dependencies": { + "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 + } + } + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "0.1.7", + "readable-stream": "2.3.6" + } + }, + "merge-source-map": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.0.4.tgz", + "integrity": "sha1-pd5GU42uhNQRTMXqArR3KmNGcB8=", + "dev": true, + "requires": { + "source-map": "0.5.7" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "brorand": "1.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 + }, + "mini-signals": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mini-signals/-/mini-signals-1.2.0.tgz", + "integrity": "sha1-RbCAE8X65RokqhqTXNMXye1yHXQ=" + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "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": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "1.6.2", + "duplexify": "3.7.1", + "end-of-stream": "1.4.1", + "flush-write-stream": "1.1.1", + "from2": "2.3.0", + "parallel-transform": "1.1.0", + "pump": "3.0.0", + "pumpify": "1.5.1", + "stream-each": "1.2.3", + "through2": "2.0.5" + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "1.0.2", + "is-extendable": "1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "2.0.4" + } + } + } + }, + "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" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, + "requires": { + "aproba": "1.2.0", + "copy-concurrently": "1.0.5", + "fs-write-stream-atomic": "1.0.10", + "mkdirp": "0.5.1", + "rimraf": "2.6.3", + "run-queue": "1.0.3" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "fragment-cache": "0.2.1", + "is-windows": "1.0.2", + "kind-of": "6.0.2", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "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 + }, + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true + }, + "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-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "dev": true, + "requires": { + "assert": "1.5.0", + "browserify-zlib": "0.2.0", + "buffer": "4.9.1", + "console-browserify": "1.1.0", + "constants-browserify": "1.0.0", + "crypto-browserify": "3.12.0", + "domain-browser": "1.2.0", + "events": "3.0.0", + "https-browserify": "1.0.0", + "os-browserify": "0.3.0", + "path-browserify": "0.0.1", + "process": "0.11.10", + "punycode": "1.4.1", + "querystring-es3": "0.2.1", + "readable-stream": "2.3.6", + "stream-browserify": "2.0.2", + "stream-http": "2.8.3", + "string_decoder": "1.1.1", + "timers-browserify": "2.0.10", + "tty-browserify": "0.0.0", + "url": "0.11.0", + "util": "0.11.1", + "vm-browserify": "1.1.0" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "node-releases": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.24.tgz", + "integrity": "sha512-wym2jptfuKowMmkZsfCSTsn8qAVo8zm+UiQA6l5dNqUcpfChZSnS/vbbpOeXczf+VdPhutxh+99lWHhdd6xKzg==", + "dev": true, + "requires": { + "semver": "5.7.0" + } + }, + "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-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "2.0.1" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "0.1.1", + "define-property": "0.2.5", + "kind-of": "3.2.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + } + } + }, + "object-hash": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz", + "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==", + "dev": true + }, + "object-inspect": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.4.1.tgz", + "integrity": "sha512-wqdhLpfCUbEsoEwl3FXwGyv8ief1k/1aUdIPCqVnupM6e8l63BEJdiF/0swtn04/8p05tG/T0FrpTlfwvljOdw==", + "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-path": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.6.0.tgz", + "integrity": "sha1-tpp9EQk3k08zbKVh/ZvhrXt+DLc=", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "3.0.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "3.0.1" + } + }, + "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" + } + }, + "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.2.0" + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "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", + "wordwrap": "1.0.0" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "1.0.0", + "lcid": "2.0.0", + "mem": "4.3.0" + } + }, + "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=", + "dev": true + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "p-try": "2.2.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.2.0" + } + }, + "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==", + "dev": true + }, + "pako": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", + "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", + "dev": true + }, + "parallel-transform": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", + "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", + "dev": true, + "requires": { + "cyclist": "0.2.2", + "inherits": "2.0.4", + "readable-stream": "2.3.6" + } + }, + "parse-asn1": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", + "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==", + "dev": true, + "requires": { + "asn1.js": "4.10.1", + "browserify-aes": "1.2.0", + "create-hash": "1.2.0", + "evp_bytestokey": "1.0.3", + "pbkdf2": "3.0.17", + "safe-buffer": "5.1.2" + } + }, + "parse-ms": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-1.0.1.tgz", + "integrity": "sha1-VjRtR0nXjyNDDKDHE4UK75GqNh0=", + "dev": true + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "parse-uri": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-uri/-/parse-uri-1.0.0.tgz", + "integrity": "sha1-KHLcwi8aeXrN4Vg9igrClVLdrCA=" + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "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=", + "dev": true + }, + "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", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "dev": true, + "requires": { + "create-hash": "1.2.0", + "create-hmac": "1.1.7", + "ripemd160": "2.0.2", + "safe-buffer": "5.1.2", + "sha.js": "2.4.11" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "pixi-gl-core": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/pixi-gl-core/-/pixi-gl-core-1.1.4.tgz", + "integrity": "sha1-i0tcQzsx5Bm8N53FZc4bg1qRs3I=" + }, + "pixi.js": { + "version": "4.8.8", + "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-4.8.8.tgz", + "integrity": "sha512-wQzuLAWSMfV+x2guL5jZBp37pwCmYXHiTXG7ZWXu4E/5IsC9xozwmOfLeCNEyPzlyucOgxAx/HS+tLqxWPYX7Q==", + "requires": { + "bit-twiddle": "1.0.2", + "earcut": "2.1.5", + "eventemitter3": "2.0.3", + "ismobilejs": "0.5.2", + "object-assign": "4.1.1", + "pixi-gl-core": "1.1.4", + "remove-array-items": "1.1.1", + "resource-loader": "2.2.4" + } + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "3.0.0" + } + }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "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 + }, + "pretty-ms": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-3.2.0.tgz", + "integrity": "sha512-ZypexbfVUGTFxb0v+m1bUyy92DHe5SyYlnyY0msyms5zd3RwyvNgyxZZsXXgoyzlxjx5MiqtXUdhUfvQbe0A2Q==", + "dev": true, + "requires": { + "parse-ms": "1.0.1" + } + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "requires": { + "asap": "2.0.6" + } + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "browserify-rsa": "4.0.1", + "create-hash": "1.2.0", + "parse-asn1": "5.1.4", + "randombytes": "2.1.0", + "safe-buffer": "5.1.2" + } + }, + "pug": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pug/-/pug-2.0.4.tgz", + "integrity": "sha512-XhoaDlvi6NIzL49nu094R2NA6P37ijtgMDuWE+ofekDChvfKnzFal60bhSdiy8y2PBO6fmz3oMEIcfpBVRUdvw==", + "dev": true, + "requires": { + "pug-code-gen": "2.0.2", + "pug-filters": "3.1.1", + "pug-lexer": "4.1.0", + "pug-linker": "3.0.6", + "pug-load": "2.0.12", + "pug-parser": "5.0.1", + "pug-runtime": "2.0.5", + "pug-strip-comments": "1.0.4" + } + }, + "pug-attrs": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-2.0.4.tgz", + "integrity": "sha512-TaZ4Z2TWUPDJcV3wjU3RtUXMrd3kM4Wzjbe3EWnSsZPsJ3LDI0F3yCnf2/W7PPFF+edUFQ0HgDL1IoxSz5K8EQ==", + "dev": true, + "requires": { + "constantinople": "3.1.2", + "js-stringify": "1.0.2", + "pug-runtime": "2.0.5" + } + }, + "pug-code-gen": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-2.0.2.tgz", + "integrity": "sha512-kROFWv/AHx/9CRgoGJeRSm+4mLWchbgpRzTEn8XCiwwOy6Vh0gAClS8Vh5TEJ9DBjaP8wCjS3J6HKsEsYdvaCw==", + "dev": true, + "requires": { + "constantinople": "3.1.2", + "doctypes": "1.1.0", + "js-stringify": "1.0.2", + "pug-attrs": "2.0.4", + "pug-error": "1.3.3", + "pug-runtime": "2.0.5", + "void-elements": "2.0.1", + "with": "5.1.1" + } + }, + "pug-error": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-1.3.3.tgz", + "integrity": "sha512-qE3YhESP2mRAWMFJgKdtT5D7ckThRScXRwkfo+Erqga7dyJdY3ZquspprMCj/9sJ2ijm5hXFWQE/A3l4poMWiQ==", + "dev": true + }, + "pug-filters": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-3.1.1.tgz", + "integrity": "sha512-lFfjNyGEyVWC4BwX0WyvkoWLapI5xHSM3xZJFUhx4JM4XyyRdO8Aucc6pCygnqV2uSgJFaJWW3Ft1wCWSoQkQg==", + "dev": true, + "requires": { + "clean-css": "4.2.1", + "constantinople": "3.1.2", + "jstransformer": "1.0.0", + "pug-error": "1.3.3", + "pug-walk": "1.1.8", + "resolve": "1.11.1", + "uglify-js": "2.8.29" + } + }, + "pug-lexer": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-4.1.0.tgz", + "integrity": "sha512-i55yzEBtjm0mlplW4LoANq7k3S8gDdfC6+LThGEvsK4FuobcKfDAwt6V4jKPH9RtiE3a2Akfg5UpafZ1OksaPA==", + "dev": true, + "requires": { + "character-parser": "2.2.0", + "is-expression": "3.0.0", + "pug-error": "1.3.3" + } + }, + "pug-linker": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-3.0.6.tgz", + "integrity": "sha512-bagfuHttfQOpANGy1Y6NJ+0mNb7dD2MswFG2ZKj22s8g0wVsojpRlqveEQHmgXXcfROB2RT6oqbPYr9EN2ZWzg==", + "dev": true, + "requires": { + "pug-error": "1.3.3", + "pug-walk": "1.1.8" + } + }, + "pug-load": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-2.0.12.tgz", + "integrity": "sha512-UqpgGpyyXRYgJs/X60sE6SIf8UBsmcHYKNaOccyVLEuT6OPBIMo6xMPhoJnqtB3Q3BbO4Z3Bjz5qDsUWh4rXsg==", + "dev": true, + "requires": { + "object-assign": "4.1.1", + "pug-walk": "1.1.8" + } + }, + "pug-parser": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-5.0.1.tgz", + "integrity": "sha512-nGHqK+w07p5/PsPIyzkTQfzlYfuqoiGjaoqHv1LjOv2ZLXmGX1O+4Vcvps+P4LhxZ3drYSljjq4b+Naid126wA==", + "dev": true, + "requires": { + "pug-error": "1.3.3", + "token-stream": "0.0.1" + } + }, + "pug-runtime": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-2.0.5.tgz", + "integrity": "sha512-P+rXKn9un4fQY77wtpcuFyvFaBww7/91f3jHa154qU26qFAnOe6SW1CbIDcxiG5lLK9HazYrMCCuDvNgDQNptw==", + "dev": true + }, + "pug-strip-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-1.0.4.tgz", + "integrity": "sha512-i5j/9CS4yFhSxHp5iKPHwigaig/VV9g+FgReLJWWHEHbvKsbqL0oP/K5ubuLco6Wu3Kan5p7u7qk8A4oLLh6vw==", + "dev": true, + "requires": { + "pug-error": "1.3.3" + } + }, + "pug-walk": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-1.1.8.tgz", + "integrity": "sha512-GMu3M5nUL3fju4/egXwZO0XLi6fW/K3T3VTgFQ14GxNi8btlxgT5qZL//JwZFm/2Fa64J/PNS8AZeys3wiMkVA==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "1.4.1", + "once": "1.4.0" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "3.7.1", + "inherits": "2.0.4", + "pump": "2.0.1" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "1.4.1", + "once": "1.4.0" + } + } + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "quote-stream": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/quote-stream/-/quote-stream-1.0.2.tgz", + "integrity": "sha1-hJY/jJwmuULhU/7rU6rnRlK34LI=", + "dev": true, + "requires": { + "buffer-equal": "0.0.1", + "minimist": "1.2.0", + "through2": "2.0.5" + } + }, + "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.2" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "2.1.0", + "safe-buffer": "5.1.2" + } + }, + "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==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.4", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "4.2.0", + "micromatch": "3.1.10", + "readable-stream": "2.3.6" + } + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz", + "integrity": "sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==", + "dev": true, + "requires": { + "regenerate": "1.4.0" + } + }, + "regenerator-runtime": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", + "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==" + }, + "regenerator-transform": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.0.tgz", + "integrity": "sha512-rtOelq4Cawlbmq9xuMR5gdFmv7ku/sFoB7sRiywx7aq53bc52b4j6zvH7Te1Vt/X2YveDKnCGUbioieU7FEL3w==", + "dev": true, + "requires": { + "private": "0.1.8" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "3.0.2", + "safe-regex": "1.1.0" + } + }, + "regexp-tree": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.10.tgz", + "integrity": "sha512-K1qVSbcedffwuIslMwpe6vGlj+ZXRnGkvjAtFHfDZZZuEdA/h0dxljAPu9vhUo6Rrx2U2AwJ+nSQ6hK+lrP5MQ==", + "dev": true + }, + "regexpp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", + "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", + "dev": true + }, + "regexpu-core": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.5.4.tgz", + "integrity": "sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==", + "dev": true, + "requires": { + "regenerate": "1.4.0", + "regenerate-unicode-properties": "8.1.0", + "regjsgen": "0.5.0", + "regjsparser": "0.6.0", + "unicode-match-property-ecmascript": "1.0.4", + "unicode-match-property-value-ecmascript": "1.1.0" + } + }, + "regjsgen": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", + "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==", + "dev": true + }, + "regjsparser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", + "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", + "dev": true, + "requires": { + "jsesc": "0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "remove-array-items": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/remove-array-items/-/remove-array-items-1.1.1.tgz", + "integrity": "sha512-MXW/jtHyl5F1PZI7NbpS8SOtympdLuF20aoWJT5lELR1p/HJDd5nqW8Eu9uLh/hCRY3FgvrIT5AwDCgBODklcA==" + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "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-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 + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "0.1.0", + "resolve-from": "1.0.1" + } + }, + "resolve": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", + "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", + "requires": { + "path-parse": "1.0.6" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "requires": { + "expand-tilde": "2.0.2", + "global-modules": "1.0.0" + }, + "dependencies": { + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "requires": { + "global-prefix": "1.0.2", + "is-windows": "1.0.2", + "resolve-dir": "1.0.1" + } + } + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "resource-loader": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/resource-loader/-/resource-loader-2.2.4.tgz", + "integrity": "sha512-MrY0bEJN26us3h4bzJUSP0n4tFEb79lCpYBavtLjSezWCcXZMgxhSgvC9LxueuqpcxG+qPjhwFu5SQAcUNacdA==", + "requires": { + "mini-signals": "1.2.0", + "parse-uri": "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.1", + "signal-exit": "3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "requires": { + "align-text": "0.1.4" + } + }, + "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.4" + } + }, + "riot": { + "version": "3.13.2", + "resolved": "https://registry.npmjs.org/riot/-/riot-3.13.2.tgz", + "integrity": "sha512-L4WFJEhbTA0deDoZ1XxoVw7Tf96+xYc06aQ+4QM+IkYClD6mZ7E/9zN3Rd48uYMBvHQfHtbPvR0KEiu3pJyI2A==", + "dev": true, + "requires": { + "riot-cli": "4.1.2", + "riot-compiler": "3.6.0", + "riot-observable": "3.0.0", + "riot-tmpl": "3.0.8", + "simple-dom": "1.3.0", + "simple-html-tokenizer": "0.5.7" + }, + "dependencies": { + "riot-cli": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/riot-cli/-/riot-cli-4.1.2.tgz", + "integrity": "sha512-JuRHDJKtGvAMksulzYHQRmOmzeICIOLe/PHvRuByfRlqGa0IP87baHATkKF4uwveMQKx3mq4JTvhfQHxilhi4g==", + "dev": true, + "requires": { + "chalk": "2.4.2", + "chokidar": "2.1.6", + "co": "4.6.0", + "optionator": "0.8.2", + "riot-compiler": "3.6.0", + "rollup": "0.57.1" + } + } + } + }, + "riot-compiler": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/riot-compiler/-/riot-compiler-3.6.0.tgz", + "integrity": "sha512-P2MVj0T9ox0EFPTSSHJIAaBe6/fCnKln76BtPK6ezAlBW2+qKCDzzvkj3gwFvGEG1dYUHa2jQ/O6+FZNNe8CYw==", + "dev": true, + "requires": { + "skip-regex": "0.3.1", + "source-map": "0.7.3", + "string-similarity": "1.2.2" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + } + } + }, + "riot-hot-reload": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/riot-hot-reload/-/riot-hot-reload-1.0.0.tgz", + "integrity": "sha1-qZ2gZZyg7/bhQHWEVOaH98BuTr4=", + "dev": true + }, + "riot-observable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/riot-observable/-/riot-observable-3.0.0.tgz", + "integrity": "sha1-i70tr3KiFBuwQwgt9AI9xQS60us=", + "dev": true + }, + "riot-route": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/riot-route/-/riot-route-3.1.4.tgz", + "integrity": "sha512-40FXoqIxF5KBRJZ26u8Abyq/JZXWHRMOqlat9wXjNKxdbLeKbSjt4vPlR8OUTjFRtp73dTdKdZ5MzHckMmaH6Q==", + "dev": true, + "requires": { + "riot-observable": "3.0.0" + } + }, + "riot-tag-loader": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/riot-tag-loader/-/riot-tag-loader-2.0.2.tgz", + "integrity": "sha512-/4viqu4d3NWibhz8gpVO+l6PTcxyH5ITpeke4cpJ/oKLcpuiOSTQdqt/JBj/nh5TrcD94CZJqWZJGMtbkWApNg==", + "dev": true, + "requires": { + "loader-utils": "1.2.3" + } + }, + "riot-tmpl": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/riot-tmpl/-/riot-tmpl-3.0.8.tgz", + "integrity": "sha1-3WVOcqOhUgywCcvvcMc4Vt7VhKY=", + "dev": true, + "requires": { + "eslint-config-riot": "1.0.0" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "3.0.4", + "inherits": "2.0.4" + } + }, + "rollup": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-0.57.1.tgz", + "integrity": "sha512-I18GBqP0qJoJC1K1osYjreqA8VAKovxuI3I81RSk0Dmr4TgloI0tAULjZaox8OsJ+n7XRrhH6i0G2By/pj1LCA==", + "dev": true, + "requires": { + "@types/acorn": "4.0.5", + "acorn": "5.7.3", + "acorn-dynamic-import": "3.0.0", + "date-time": "2.1.0", + "is-reference": "1.1.2", + "locate-character": "2.0.5", + "pretty-ms": "3.2.0", + "require-relative": "0.8.7", + "rollup-pluginutils": "2.8.1", + "signal-exit": "3.0.2", + "sourcemap-codec": "1.4.4" + } + }, + "rollup-pluginutils": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.1.tgz", + "integrity": "sha512-J5oAoysWar6GuZo0s+3bZ6sVZAC0pfqKz68De7ZgDi5z63jOVZn1uJL/+z1jeKHNbGII8kAyHF5q8LnxSX5lQg==", + "dev": true, + "requires": { + "estree-walker": "0.6.1" + } + }, + "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" + } + }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "dev": true, + "requires": { + "aproba": "1.2.0" + } + }, + "rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "dev": true + }, + "rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "dev": true, + "requires": { + "rx-lite": "4.0.8" + } + }, + "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-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "0.1.15" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "6.10.0", + "ajv-errors": "1.0.1", + "ajv-keywords": "3.4.0" + }, + "dependencies": { + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "dev": true, + "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" + } + }, + "ajv-keywords": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz", + "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==", + "dev": true + }, + "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=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + } + } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + }, + "serialize-javascript": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.7.0.tgz", + "integrity": "sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "split-string": "3.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "2.0.4", + "safe-buffer": "5.1.2" + } + }, + "shallow-copy": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", + "integrity": "sha1-QV9CcC1z2BAzApLMXuhurhoRoXA=", + "dev": true + }, + "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 + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "simple-dom": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/simple-dom/-/simple-dom-1.3.0.tgz", + "integrity": "sha512-RVjr6e80FFGDqDJZeQd4EMwoDLatn4Jy2SfuXecrP1IgG4ZAqkGSokE8LNV5i0kzWR2IM0e257xGN9JS8lxm0Q==", + "dev": true + }, + "simple-html-tokenizer": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/simple-html-tokenizer/-/simple-html-tokenizer-0.5.7.tgz", + "integrity": "sha512-APW9iYbkJ5cijjX4Ljhf3VG8SwYPUJT5gZrwci/wieMabQxWFiV5VwsrP5c6GMRvXKEQMGkAB1d9dvW66dTqpg==", + "dev": true + }, + "skip-regex": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/skip-regex/-/skip-regex-0.3.1.tgz", + "integrity": "sha1-F5GarirEzj1h1ed+7diCBsZKohU=", + "dev": true + }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0" + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "0.11.2", + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "map-cache": "0.2.2", + "source-map": "0.5.7", + "source-map-resolve": "0.5.2", + "use": "3.1.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "1.0.0", + "isobject": "3.0.1", + "snapdragon-util": "3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "sort-by": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/sort-by/-/sort-by-1.2.0.tgz", + "integrity": "sha1-7ZK7/5/SKEtB9lA+OElmB7Il/m8=", + "dev": true, + "requires": { + "object-path": "0.6.0" + } + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "2.1.2", + "decode-uri-component": "0.2.0", + "resolve-url": "0.2.1", + "source-map-url": "0.4.0", + "urix": "0.1.0" + } + }, + "source-map-support": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "dev": true, + "requires": { + "buffer-from": "1.1.1", + "source-map": "0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "sourcemap-codec": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz", + "integrity": "sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg==", + "dev": true + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "3.0.2" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "3.5.1" + } + }, + "static-eval": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", + "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", + "dev": true, + "requires": { + "escodegen": "1.9.1" + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "0.2.5", + "object-copy": "0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + } + } + }, + "static-module": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/static-module/-/static-module-2.2.5.tgz", + "integrity": "sha512-D8vv82E/Kpmz3TXHKG8PPsCPg+RAX6cbCOyvjM6x04qZtQ47EtJFVwRsdov3n5d6/6ynrOY9XB4JkaZwB2xoRQ==", + "dev": true, + "requires": { + "concat-stream": "1.6.2", + "convert-source-map": "1.6.0", + "duplexer2": "0.1.4", + "escodegen": "1.9.1", + "falafel": "2.1.0", + "has": "1.0.3", + "magic-string": "0.22.5", + "merge-source-map": "1.0.4", + "object-inspect": "1.4.1", + "quote-stream": "1.0.2", + "readable-stream": "2.3.6", + "shallow-copy": "0.0.1", + "static-eval": "2.0.2", + "through2": "2.0.5" + } + }, + "stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dev": true, + "requires": { + "inherits": "2.0.4", + "readable-stream": "2.3.6" + } + }, + "stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "dev": true, + "requires": { + "end-of-stream": "1.4.1", + "stream-shift": "1.0.0" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "requires": { + "builtin-status-codes": "3.0.0", + "inherits": "2.0.4", + "readable-stream": "2.3.6", + "to-arraybuffer": "1.0.1", + "xtend": "4.0.1" + } + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + }, + "string-similarity": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-1.2.2.tgz", + "integrity": "sha512-IoHUjcw3Srl8nsPlW04U3qwWPk3oG2ffLM0tN853d/E/JlIvcmZmDY2Kz5HzKp4lEi2T7QD7Zuvjq/1rDw+XcQ==", + "dev": true, + "requires": { + "lodash.every": "4.6.0", + "lodash.flattendeep": "4.4.0", + "lodash.foreach": "4.5.0", + "lodash.map": "4.6.0", + "lodash.maxby": "4.6.0" + } + }, + "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" + } + }, + "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==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "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" + }, + "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 + } + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "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=", + "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==", + "requires": { + "has-flag": "3.0.0" + } + }, + "table": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "dev": true, + "requires": { + "ajv": "5.5.2", + "ajv-keywords": "2.1.1", + "chalk": "2.4.2", + "lodash": "4.17.11", + "slice-ansi": "1.0.0", + "string-width": "2.1.1" + } + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true + }, + "terser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.0.2.tgz", + "integrity": "sha512-IWLuJqTvx97KP3uTYkFVn93cXO+EtlzJu8TdJylq+H0VBDlPMIfQA9MBS5Vc5t3xTEUG1q0hIfHMpAP2R+gWTw==", + "dev": true, + "requires": { + "commander": "2.20.0", + "source-map": "0.6.1", + "source-map-support": "0.5.12" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "terser-webpack-plugin": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.3.0.tgz", + "integrity": "sha512-W2YWmxPjjkUcOWa4pBEv4OP4er1aeQJlSo2UhtCFQCuRXEHjOFscO8VyWHj9JLlA0RzQb8Y2/Ta78XZvT54uGg==", + "dev": true, + "requires": { + "cacache": "11.3.3", + "find-cache-dir": "2.1.0", + "is-wsl": "1.1.0", + "loader-utils": "1.2.3", + "schema-utils": "1.0.0", + "serialize-javascript": "1.7.0", + "source-map": "0.6.1", + "terser": "4.0.2", + "webpack-sources": "1.3.0", + "worker-farm": "1.7.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "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", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "2.3.6", + "xtend": "4.0.1" + } + }, + "time-zone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz", + "integrity": "sha1-mcW/VZWJZq9tBtg73zgA3IL67F0=", + "dev": true + }, + "timers-browserify": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", + "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", + "dev": true, + "requires": { + "setimmediate": "1.0.5" + } + }, + "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-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "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=" + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "regex-not": "1.0.2", + "safe-regex": "1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "3.0.0", + "repeat-string": "1.6.1" + } + }, + "token-stream": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz", + "integrity": "sha1-zu78cXp2xDFvEm0LnbqlXX598Bo=", + "dev": true + }, + "transform-loader": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/transform-loader/-/transform-loader-0.2.4.tgz", + "integrity": "sha1-5ch4d7qW1R0/IlNoWHtG4ibRzsk=", + "dev": true, + "requires": { + "loader-utils": "1.2.3" + } + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" + }, + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "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" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "requires": { + "source-map": "0.5.7", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, + "optional": true + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "1.0.4", + "unicode-property-aliases-ecmascript": "1.0.5" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz", + "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz", + "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==", + "dev": true + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "3.1.0", + "get-value": "2.0.6", + "is-extendable": "0.1.1", + "set-value": "2.0.1" + } + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "2.0.2" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "requires": { + "imurmurhash": "0.1.4" + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "0.3.1", + "isobject": "3.0.1" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "2.0.6", + "has-values": "0.1.4", + "isobject": "2.1.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "upath": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", + "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "2.1.1" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "dev": true, + "requires": { + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "v8-compile-cache": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz", + "integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==", + "dev": true + }, + "vlq": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz", + "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==", + "dev": true + }, + "vm-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz", + "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==", + "dev": true + }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "dev": true + }, + "watchpack": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", + "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", + "dev": true, + "requires": { + "chokidar": "2.1.6", + "graceful-fs": "4.2.0", + "neo-async": "2.6.1" + } + }, + "webpack": { + "version": "4.35.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.35.2.tgz", + "integrity": "sha512-TZAmorNymV4q66gAM/h90cEjG+N3627Q2MnkSgKlX/z3DlNVKUtqy57lz1WmZU2+FUZwzM+qm7cGaO95PyrX5A==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-module-context": "1.8.5", + "@webassemblyjs/wasm-edit": "1.8.5", + "@webassemblyjs/wasm-parser": "1.8.5", + "acorn": "6.1.1", + "acorn-dynamic-import": "4.0.0", + "ajv": "6.10.0", + "ajv-keywords": "3.4.0", + "chrome-trace-event": "1.0.2", + "enhanced-resolve": "4.1.0", + "eslint-scope": "4.0.3", + "json-parse-better-errors": "1.0.2", + "loader-runner": "2.4.0", + "loader-utils": "1.2.3", + "memory-fs": "0.4.1", + "micromatch": "3.1.10", + "mkdirp": "0.5.1", + "neo-async": "2.6.1", + "node-libs-browser": "2.2.1", + "schema-utils": "1.0.0", + "tapable": "1.1.3", + "terser-webpack-plugin": "1.3.0", + "watchpack": "1.6.0", + "webpack-sources": "1.3.0" + }, + "dependencies": { + "acorn": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", + "dev": true + }, + "acorn-dynamic-import": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz", + "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==", + "dev": true + }, + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "dev": true, + "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" + } + }, + "ajv-keywords": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz", + "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==", + "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.2.1", + "estraverse": "4.2.0" + } + }, + "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=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + } + } + }, + "webpack-cli": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.5.tgz", + "integrity": "sha512-w0j/s42c5UhchwTmV/45MLQnTVwRoaUTu9fM5LuyOd/8lFoCNCELDogFoecx5NzRUndO0yD/gF2b02XKMnmAWQ==", + "dev": true, + "requires": { + "chalk": "2.4.2", + "cross-spawn": "6.0.5", + "enhanced-resolve": "4.1.0", + "findup-sync": "3.0.0", + "global-modules": "2.0.0", + "import-local": "2.0.0", + "interpret": "1.2.0", + "loader-utils": "1.2.3", + "supports-color": "6.1.0", + "v8-compile-cache": "2.0.3", + "yargs": "13.2.4" + }, + "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 + }, + "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" + } + }, + "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.5", + "path-key": "2.0.1", + "semver": "5.7.0", + "shebang-command": "1.2.0", + "which": "1.3.1" + } + }, + "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.3", + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "5.2.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" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + }, + "yargs": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz", + "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==", + "dev": true, + "requires": { + "cliui": "5.0.0", + "find-up": "3.0.0", + "get-caller-file": "2.0.5", + "os-locale": "3.1.0", + "require-directory": "2.1.1", + "require-main-filename": "2.0.0", + "set-blocking": "2.0.0", + "string-width": "3.1.0", + "which-module": "2.0.0", + "y18n": "4.0.0", + "yargs-parser": "13.1.1" + } + } + } + }, + "webpack-sources": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", + "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", + "dev": true, + "requires": { + "source-list-map": "2.0.1", + "source-map": "0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true + }, + "with": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/with/-/with-5.1.1.tgz", + "integrity": "sha1-+k2qktrzLE6pTtRTyB8EaGtXXf4=", + "dev": true, + "requires": { + "acorn": "3.3.0", + "acorn-globals": "3.1.0" + }, + "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 + } + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "dev": true, + "requires": { + "errno": "0.1.7" + } + }, + "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.1", + "string-width": "3.1.0", + "strip-ansi": "5.2.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 + }, + "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.3", + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "5.2.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", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "0.5.1" + } + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "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": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.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.3.1", + "decamelize": "1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } + } + } + } +} diff --git a/ui/app/package.json b/ui/app/package.json new file mode 100644 index 000000000..3fa66f7d0 --- /dev/null +++ b/ui/app/package.json @@ -0,0 +1,54 @@ +{ + "name": "postgres-operator-ui", + "version": "1.0.0", + "description": "PostgreSQL Operator UI", + "main": "src/app.js", + "config": { + "buildDir": "../operator_ui/static/build" + }, + "scripts": { + "prestart": "npm install", + "start": "NODE_ENV=development webpack --watch", + "webpack": "webpack --config ./webpack.config.js", + "build": "NODE_ENV=development npm run webpack", + "prewebpack": "npm run clean", + "lint": "eslint ./src/**/*.js", + "clean": "rimraf $npm_package_config_buildDir && mkdir $npm_package_config_buildDir" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/zalando/postgres-operator.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/zalando/postgres-operator.git/issues" + }, + "homepage": "https://github.com/zalando/postgres-operator.git#readme", + "dependencies": { + "@babel/core": "^7.0.0-beta.46", + "@babel/polyfill": "^7.0.0-beta.46", + "@babel/runtime": "^7.0.0-beta.46", + "pixi.js": "^4.7.3" + }, + "devDependencies": { + "@babel/plugin-transform-runtime": "^7.0.0-beta.46", + "@babel/preset-env": "^7.0.0-beta.46", + "babel-loader": "^8.0.0-beta.2", + "brfs": "^1.6.1", + "dedent-js": "1.0.1", + "eslint": "^4.19.1", + "eslint-loader": "^1.6.1", + "js-yaml": "3.13.1", + "pug": "^2.0.3", + "rimraf": "^2.5.4", + "riot": "^3.9.5", + "riot-hot-reload": "1.0.0", + "riot-route": "^3.1.3", + "riot-tag-loader": "2.0.2", + "sort-by": "^1.2.0", + "transform-loader": "^0.2.3", + "webpack": "^4.28.2", + "webpack-cli": "^3.1.2" + } +} diff --git a/ui/app/src/app.js b/ui/app/src/app.js new file mode 100644 index 000000000..9eb7569e6 --- /dev/null +++ b/ui/app/src/app.js @@ -0,0 +1,240 @@ +import jQuery from 'jquery' +import riot from 'riot' + +import 'riot-hot-reload' + +import './edit.tag.pug' +import './postgresql.tag.pug' +import './help-edit.tag.pug' +import './help-general.tag.pug' +import './postgresqls.tag.pug' +import './logs.tag.pug' +import './new.tag.pug' +import './status.tag.pug' +import './app.tag.pug' +import './restore.tag.pug' + +Object.fromEntries = entries => entries.length === 0 ? {} : Object.assign(...entries.map(([k, v]) => ({[k]: v}))) +Object.mapValues = (o, f) => Object.fromEntries(Object.entries(o).map(([k, v]) => [k, f(v, k)])) +Object.mapEntries = (o, f) => Object.fromEntries(Object.entries(o).map(f).filter(x => x)) +Object.filterEntries = (o, f) => Object.mapEntries(o, entry => f(entry) && entry) +Object.filterValues = (o, f) => Object.filterEntries(o, ([key, value]) => f(value) && [key, value]) + + +const getDefaulting = (object, key, def) => ( + object.hasOwnProperty(key) ? object[key] : def +) + + +const Dynamic = (options={}) => { + const instance = { + init: getDefaulting(options, 'init', () => ''), + refresh: getDefaulting(options, 'refresh', () => true), + update: getDefaulting(options, 'update', value => (instance.state = value, true)), + validState: getDefaulting(options, 'validState', state => ( + state !== undefined && + state !== null && + typeof state === 'string' && + state.length > 0 + )), + + edit: event => (instance.update(event.target.value, instance, event), true), + valid: () => instance.validState(instance.state), + } + + instance.state = instance.init() + return instance +} + + +/* +Dynamics manages a dynamic array whose elements are themselves Dynamic objects. + +The default initializer builds an empty array as the initial state. + +The "add" DOM event callback is provided to add a newly initialized Dynamic +object to the end of the state array. The Dynamic array item is initialized +with the "itemInit" callback, which can be specified with a constructor option +and defaults to creating a Dynamic with all default options. + +The "remove" DOM event callback is provided to handle DOM events that should +or remove a specific item. For events on elements generated by iterating the +state with an each= attribute, the event.item will be set to the correct value. + +The refresh callback is forwarded to all constituent Dynamic objects. +*/ +const Dynamics = (options={}) => { + const instance = Object.assign( + Dynamic( + Object.assign( + { init: () => [] }, + 'refresh' in options + ? { refresh: options.refresh } + : undefined + ) + ), + + { + itemInit: getDefaulting(options, 'itemInit', () => + Dynamic( + 'refresh' in options + ? { refresh: options.refresh } + : {} + ) + ), + itemValid: getDefaulting(options, 'itemValid', item => item.valid()), + validState: state => state.every(instance.itemValid), + update: () => true, + edit: () => true, + + add: _event => { + instance.state.push(instance.itemInit()) + instance.refresh() + return true + }, + + remove: event => { + instance.state.splice(instance.state.indexOf(event.item), 1) + instance.refresh() + return true + }, + } + ) + + Object.defineProperty(instance, 'valids', { get: () => + instance.state.filter(instance.itemValid) + }) + + return instance +} + + +/* +DynamicSet manages a keyed collection of Dynamic objects. The constructor +receives an object mapping keys to initialization functions, and its state is a +mapping from the same keys to Dynamics initialized using the corresponding +key's initialization function. A DynamicSet is valid when its constituent +Dynamics are all simultaneously valid. The refresh callback is forwarded to +all constituent Dynamic objects. + +Example: + + DynamicSet({ + foo: undefined, + bar: () => 'baz', + }) + +This call would create a DynamicSet with two constituent dynamics in its state: +one of them under the 'foo' key of the state object, built with the default +Dynamic initializer, and another under the 'bar' key of the state object, whose +state, in turn, would initially hold the value 'baz'. +*/ +const DynamicSet = (items, options={}) => Object.assign( + Dynamic( + Object.assign( + { + init: () => Object.mapValues(items, init => + Dynamic( + Object.assign( + init ? { init: init } : undefined, + 'refresh' in options ? { refresh: options.refresh } : undefined + ) + ) + ), + }, + 'refresh' in options + ? { refresh: options.refresh } + : undefined + ) + ), + + { + items: items, + validState: state => Object.values(state).every(item => item.valid()), + edit: () => true, + update: () => true, + } +) + + +const delete_cluster = (namespace, clustername) => { + jQuery.confirm({ + backgroundDismiss: true, + content: ` +

+ Are you sure you want to remove this PostgreSQL cluster? If so, + please type the cluster name here + (${namespace}/${clustername}) and click the + confirm button: +

+ +
+

+ Note: if you create a cluster with the same name as + this one after deleting it, the new cluster will restore the data + from this cluster's current backups stored in AWS S3. This behavior + will change soon and you will be able to reuse a cluster name and + get a completely new cluster. +

+ `, + escapeKey: true, + icon: 'glyphicon glyphicon-warning-sign', + title: 'Confirm cluster deletion?', + typeAnimated: true, + type: 'red', + onOpen: function () { + const dialog = this + const confirm = dialog.buttons.confirm + const confirmSelector = jQuery(confirm.el) + const input = dialog.$content.find('input') + input.on('input', () => { + if (input.val() === namespace + '/' + clustername) { + confirmSelector.removeClass('btn-default').addClass('btn-danger') + confirm.enable() + } else { + confirm.disable() + confirmSelector.removeClass('btn-danger').addClass('btn-default') + } + }) + }, + buttons: { + cancel: { + text: 'Cancel', + }, + confirm: { + btnClass: 'btn-default', + isDisabled: true, + text: 'Delete cluster', + action: () => { + jQuery.ajax({ + type: 'DELETE', + url: ( + '/postgresqls/' + + encodeURI(namespace) + + '/' + encodeURI(clustername) + ), + dataType: 'text', + success: () => location.assign('/#/list'), + error: (r, status, error) => location.assign('/#/list'), // TODO: show error + }) + }, + }, + } + }) +} + + +/* Unfortunately, there does not appear to be a good way to import local modules +inside a Riot tag, so we define/import things here and pass them manually in the +opts variable. Remember to propagate opts manually when instantiating tags. */ +riot.mount('app', { + Dynamic: Dynamic, + Dynamics: Dynamics, + DynamicSet: DynamicSet, + delete_cluster: delete_cluster, +}) diff --git a/ui/app/src/app.tag.pug b/ui/app/src/app.tag.pug new file mode 100644 index 000000000..365371e63 --- /dev/null +++ b/ui/app/src/app.tag.pug @@ -0,0 +1,208 @@ +app + + nav.navbar.navbar-inverse.navbar-fixed-top + .container + + .navbar-header + a.navbar-brand(href='/') + | PostgreSQL Operator UI + + #navbar.navbar-collapse.collapse + ul.nav.navbar-nav + + li(class='{ active: ["EDIT", "LIST", "LOGS", "STATUS"].includes(activenav) }') + a(href='/#/list') PostgreSQL clusters + + li(class='{ active: "BACKUPS" === activenav }') + a(href='/#/backups') Backups + + li(class='{ active: "OPERATOR" === activenav }') + a(href='/#/operator') Status + + li(class='{ active: "NEW" === activenav }') + a(href='/#/new') New cluster + + li(if='{ config }') + a(href='{ config.docs_link }' target='_blank') Documentation + + .container-fluid + + .alert.alert-warning.alert-dismissible( + if='{ config && config.kubernetes_in_maintenance }' + role='alert' + ) + button.close( + aria-label='Close' + data-dismiss='alert' + type='button' + ) + span.glyphicon.glyphicon-remove(aria-hidden='true') + + p.lead + span.glyphicon.glyphicon-exclamation-sign(aria-hidden='true') + span.sr-only Warning: + | + | This Kubernetes cluster appears to be undergoing maintenance. You may experience delays in database cluster creation and changes. + + .sk-spinner-pulse( + if='{ config !== null && teams !== null && (config === undefined || teams === undefined) }' + ) + + p(if='{ config === null || teams === null }') + | Error loading UI configuration. Please + | + a(onclick="window.location.reload(true);") try again + | + | or + | + a(href="/") start over + | . + + div(if='{ config }') + + edit( + if='{ activenav === "EDIT" }' + clustername='{ clustername }' + config='{ config }' + namespace='{ namespace }' + opts='{ opts }' + read_write='{ read_write }' + teams='{ teams }' + ) + + logs( + if='{ activenav === "LOGS"}' + clustername='{ clustername }' + config='{ config }' + namespace='{ namespace }' + opts='{ opts }' + read_write='{ read_write }' + teams='{ teams }' + ) + + new( + if='{ activenav === "NEW" }' + backup_name='{ backup_name }' + backup_timestamp='{ backup_timestamp }' + backup_uid='{ backup_uid }' + config='{ config }' + opts='{ opts }' + read_write='{ read_write }' + teams='{ teams }' + ) + + postgresql( + if='{ activenav === "STATUS" }' + clustername='{ clustername }' + config='{ config }' + namespace='{ namespace }' + opts='{ opts }' + read_write='{ read_write }' + teams='{ teams }' + ) + + postgresqls( + if='{ activenav === "LIST" }' + config='{ config }' + opts='{ opts }' + read_write='{ read_write }' + teams='{ teams }' + ) + + restore( + if='{ activenav === "BACKUPS" }' + config='{ config }' + opts='{ opts }' + read_write='{ read_write }' + teams='{ teams }' + ) + + status( + if='{ activenav === "OPERATOR" }' + config='{ config }' + opts='{ opts }' + read_write='{ read_write }' + teams='{ teams }' + ) + + script. + + this.config = undefined + this.teams = undefined + + this.activenav = 'INIT' + this.read_write = false + + const nav = (path, page, parameters, f) => { + route(path, (...args) => { + parameters && parameters.forEach((parameter, index) => + this[parameter] = args[index] + ) + this.activenav = page + this.update() + f && f(...args) + }) + } + + const navs = (paths, ...args) => ( + paths.forEach(path => + nav(path, ...args) + ) + ) + + ;( + jQuery + .get('/config') + .done(config => { + this.config = config + ;( + jQuery + .get('/teams') + .done(teams => { + this.teams = teams.sort() + this.team = this.teams[0] + + this.read_write = ( + this.config + && ( + !this.config.read_only_mode + || this.teams.includes(this.config.superuser_team) + ) + ) + + nav('/backups', 'BACKUPS') + nav('/edit/*/*', 'EDIT', ['namespace', 'clustername']) + nav('/list', 'LIST') + nav('/logs/*/*', 'LOGS', ['namespace', 'clustername']) + nav('/operator..', 'OPERATOR') + nav('/status/*/*', 'STATUS', ['namespace', 'clustername']) + + nav( + '/new', + 'NEW', + ['backup_name', 'backup_uid', 'backup_timestamp'], + () => this.tags['new'].reset_form(), + ) + + navs( + [ + '/clone/*/*', + '/clone/*/*/*', + ], + 'NEW', + ['backup_name', 'backup_uid', 'backup_timestamp'] + ) + + route.start(true) + + if (this.activenav === 'INIT') { + route(this.read_write ? '/new' : '/list') + } + }) + .fail(() => this.teams = null) + .always(() => this.update()) + ) + }) + .fail(() => this.config = null) + .always(() => this.update()) + ) diff --git a/ui/app/src/edit.tag.pug b/ui/app/src/edit.tag.pug new file mode 100644 index 000000000..9029594bd --- /dev/null +++ b/ui/app/src/edit.tag.pug @@ -0,0 +1,205 @@ +edit + .container-fluid + + h1.page-header + nav(aria-label="breadcrumb") + ol.breadcrumb + + li.breadcrumb-item + a(href='/#/list') + | PostgreSQL clusters + + li.breadcrumb-item(if='{ cluster_path }') + a(href='/#/status/{ cluster_path }') + | { qname } + + li.breadcrumb-item.active( + aria-current='page' + if='{ cluster_path }' + ) + a(href='/#/edit/{ cluster_path }') + | Edit + + .row(if='{ cluster_path }') + + .col-lg-4 + h2 Cluster YAML definition + div + pre + code.language-yaml(ref='yamlNice') + + div + .col-lg-5 + + h3 Edit supported properties + textarea.textarea( + style='width: 100%; height: 200px; font-family: monospace' + ref='changedProperties' + onkeyup='{ updateEditable }' + onchange='{ updateEditable }' + ) + | { editablePropertiesText } + + h3 Preview changes to spec + pre + code.language-yaml(ref='yamlEditable') + | { editablePropertiesPreview } + + button.btn.btn-success( + if='{ opts.read_write }' + value='Save changes' + onclick='{ saveChanges }' + ) + | Apply changes + + .alert.alert-danger(if='{ saveResult === false }') + b Error + | + | { saveMessage } + + .alert.alert-success(if='{ saveResult === true }') + b Changes applied + | + | updates to cluster pending + + .col-lg-3 + help-edit(config='{ opts.config }') + + script. + + const yamlParser = require('js-yaml') + + this.updateEditable = e => { + if (this.refs.changedProperties.value) { + this.editablePropertiesPreview = yamlParser.safeDump( + yamlParser.safeLoad( + this.refs.changedProperties.value, + ), + ) + } + } + + this.saveChanges = e => { + this.saving = true + this.saveResult = null + this.saveMessage = '' + + jsonPayload = JSON.stringify( + yamlParser.safeLoad( + this.refs.changedProperties.value, + ), + ) + + jQuery.ajax({ + type: 'POST', + url: '/postgresqls/' + this.cluster_path, + contentType:"application/json", + data: jsonPayload, + processData: false, + success: ()=> { + this.saveResult = true + this.update() + this.pollProgress() + }, + error: (r, status, error) => { + this.saveResult = false + this.saveMessage = r.responseJSON.error + this.update() + this.pollProgress() + }, + dataType: "json", + }) + } + + this.pollProgress = () => { + jQuery.get( + '/postgresqls/' + this.cluster_path, + ).then(data => { + + // Input data: + const i = {} + this.progress.thirdParty = true + i.postgresql = this.progress.thirdPartySpec = data + i.metadata = i.postgresql.metadata + i.spec = i.postgresql.spec + + if (i.metadata.selfLink) { delete i.metadata.selfLink } + if (i.metadata.uid) { delete i.metadata.uid } + if (i.metadata.resourceVersion) { delete i.metadata.resourceVersion } + + this.update() + this.refs.yamlNice.innerHTML = yamlParser.safeDump(i.postgresql, {sortKeys: true}) + + // Output data: + const o = this.editableProperties = { spec: {} } + + o.spec.allowedSourceRanges = i.spec.allowedSourceRanges || [] + o.spec.numberOfInstances = i.spec.numberOfInstances + o.spec.enableMasterLoadBalancer = i.spec.enableMasterLoadBalancer || false + o.spec.enableReplicaLoadBalancer = i.spec.enableReplicaLoadBalancer || false + o.spec.volume = { size: i.spec.volume.size } + + if ('users' in i.spec && typeof i.spec.users === 'object') { + o.spec.users = Object.mapValues(i.spec.users, roleFlags => + !Array.isArray(roleFlags) + ? [] + : roleFlags.filter(roleFlag => typeof roleFlag === 'string') + ) + } + + if ('databases' in i.spec && typeof i.spec.databases === 'object') { + o.spec.databases = Object.filterValues(i.spec.databases, owner => + typeof owner === 'string' + ) + } + + if ('resources' in i.spec && typeof i.spec.resources === 'object') { + o.spec.resources = {}; + ['limits', 'requests'].forEach(section => { + const resources = i.spec.resources[section] + o.spec.resources[section] = {} + if (typeof resources === 'object') { + [ + 'cpu', + 'memory', + ].forEach(resourceType => { + if (resourceType in resources) { + const resourceClaim = resources[resourceType] + if (typeof resourceClaim === '') { + o.spec.resources[section][resourceType] = resources[resourceType] + } + } + }) + } + }) + } + + this.editablePropertiesText = ( + yamlParser + .safeDump(this.editableProperties) + .slice(0, -1) + ) + this.editablePropertiesPreview = this.editablePropertiesText + + this.update() + }) + } + + this.on('mount', () => { + const namespace = this.namespace = this.opts.namespace + const clustername = this.clustername = this.opts.clustername + const qname = this.qname = namespace + '/' + clustername + const cluster_path = this.cluster_path = ( + encodeURI(namespace) + + '/' + encodeURI(clustername) + ) + this.progress = { + requestStatus: 'OK', + } + this.pollProgress() + }) + + this.on('updated', () => { + this.refs.yamlEditable.innerHTML = this.editablePropertiesPreview + Prism.highlightAll() + }) diff --git a/ui/app/src/help-edit.tag.pug b/ui/app/src/help-edit.tag.pug new file mode 100644 index 000000000..ed58d7824 --- /dev/null +++ b/ui/app/src/help-edit.tag.pug @@ -0,0 +1,29 @@ +help-edit + + h2 Help + + .well + + h3(style='margin-top: 0') + | Docs + + a(href="{ opts.config.docs_link }") + | more... + + h3 Editing + + p. + The text box shows you the properties that can currently edit. Verify that the preview shows a valid part of the spec before submitting. After a successful submit the changes may take some time to be applied. + + h3 Volume size + + p. + You need to specify in format "123Gi". You can only increase the volume size. You can only increase volume size once very 6 hours, per AWS limitation. + + virtual( + if='{ opts.config.static_network_whitelist && Object.keys(opts.config.static_network_whitelist).length > 0 }' + ) + h3 IP Ranges + + // Raw tags are required here, as otherwise either riotjs removes space it shouldn't, or pugjs adds space it shouldn't. And it has to be all in one line, as it has to be a pre tag (otherwise the riotjs compiler breaks the whitespace). +

# { network }
- { range }
diff --git a/ui/app/src/help-general.tag.pug b/ui/app/src/help-general.tag.pug new file mode 100644 index 000000000..1af25f1a8 --- /dev/null +++ b/ui/app/src/help-general.tag.pug @@ -0,0 +1,18 @@ +help-general + + h2 Help + + .well + + h3(style='margin-top: 0') + | Docs + + a(href="{ opts.config.docs_link }") + | more... + + h3 Basics + + p. + The PostgreSQL operator will use your definition to create a new + PostgreSQL cluster for you. You can either copy the yaml definition + to the repositiory or you can just hit create cluster. diff --git a/ui/app/src/logs.tag.pug b/ui/app/src/logs.tag.pug new file mode 100644 index 000000000..e25cc979e --- /dev/null +++ b/ui/app/src/logs.tag.pug @@ -0,0 +1,81 @@ +logs + + h1.page-header(if='{ cluster_path }') + nav(aria-label="breadcrumb") + ol.breadcrumb + + li.breadcrumb-item + a(href='/#/list') + | PostgreSQL clusters + + li.breadcrumb-item + a(href='/#/status/{ cluster_path }') + | { qname } + + li.breadcrumb-item + a(href='/#/logs/{ cluster_path }') + | Logs + + .sk-spinner-pulse(if='{ logs === undefined }') + + .container-fluid(if='{ logs === null }') + p + | Error loading logs. Please + | + a(onclick="window.location.reload(true)") try again + | + | or + | + a(href="/") start over + | . + + .container-fluid(if='{ logs }') + + table.table.table-hover + + tr(each='{ logs }') + + td(each='{ [levels[Level]] }') + span.label.label-font-size(class='label-{ color_class }') + | { label } + + td(style='white-space: pre') + | { Time } + + td(style='font-family: monospace') + | { Message } + + script. + + this.levels = { + "panic": { label: "Panic" , color_class: "danger" }, + "fatal": { label: "Fatal" , color_class: "danger" }, + "error": { label: "Error" , color_class: "danger" }, + "warning": { label: "Warning", color_class: "warning" }, + "info": { label: "Info" , color_class: "primary" }, + "debug": { label: "Debug" , color_class: "warning" }, + } + + this.logs = undefined + + this.on('mount', () => { + if ( + this.namespace !== this.opts.namespace + || this.clustername !== this.opts.clustername + ) { + const namespace = this.namespace = this.opts.namespace + const clustername = this.clustername = this.opts.clustername + const qname = this.qname = namespace + '/' + clustername + const cluster_path = this.cluster_path = ( + encodeURI(namespace) + + '/' + encodeURI(clustername) + ) + ;( + jQuery + .get(`/operator/clusters/${cluster_path}/logs`) + .done(logs => this.logs = logs.reverse()) + .fail(() => this.logs = null) + .always(() => this.update()) + ) + } + }) diff --git a/ui/app/src/new.tag.pug b/ui/app/src/new.tag.pug new file mode 100644 index 000000000..bd0cc764e --- /dev/null +++ b/ui/app/src/new.tag.pug @@ -0,0 +1,937 @@ +new + + style. + + .input-units { + width: 2em; + } + + .resource-type { + text-align: left; + width: 7em; + } + + .container-fluid + + h1.page-header + nav(aria-label='breadcrumb') + ol.breadcrumb + + li.breadcrumb-item + a(href='/#/new') + | New PostgreSQL cluster + + .row.text-center(if='{ !creating }') + + .row + + .col-lg-3 + h2 Cluster YAML definition + div + pre + code.language-yaml(ref='yamlNice') + + .col-lg-6 + + form( + if='{ !creating }' + action='javascript:void(0);' + id='form' + ) + + .btn-toolbar.pull-right + .btn-group( + aria-label='Actions' + role='group' + ) + + input.btn.btn-primary( + type='submit' + form='form' + value='Validate' + ) + + button.btn.btn-info.btn-copy + | Copy definiton + + button.btn.btn-success( + if='{ !clusterExists && parent.read_write }' + ref='submitbutton' + onclick='{ requestCreate }' + disabled='{ !allValid() }' + ) + | Create cluster + + a.btn.btn-small.btn-warning( + if='{ clusterExists }' + href='/#/status/{ namespace.state }/{ team }-{ name }' + ) + | Cluster exists (show status) + + h2 New cluster configuration + + table.table + + tr( + each='{ backup.state.type.state === "empty" ? [] : [backup] }' + ) + td(colspan='2') + h3.text-center Source data + + tr( + each='{ backup.state.type.state === "empty" ? [] : [backup] }' + ) + td Backup name + td + input.form-control( + each='{ !["restore", "pitr"].includes(state.type.state) ? [] : [state.name] }' + ref='backup_name' + type='text' + placeholder='Source backup name' + required + value='{ state }' + onchange='{ edit }' + onkeyup='{ edit }' + ) + + tr( + each='{ backup.state.type.state === "empty" ? [] : [backup] }' + ) + td Backup UID + td + input.form-control( + each='{ !["restore", "pitr"].includes(state.type.state) ? [] : [state.uid] }' + ref='backup_uid' + type='text' + placeholder='Source backup ID' + value='{ state }' + onchange='{ edit }' + onkeyup='{ edit }' + ) + + tr( + each='{ backup.state.type.state === "empty" ? [] : [backup] }' + ) + td Target timestamp + td + input.timestamp.form-control( + each='{ !["pitr"].includes(state.type.state) ? [] : [state.timestamp] }' + ref='backup_timestamp' + type='text' + placeholder='Restore to state at timestamp' + value='{ state }' + onchange='{ edit }' + onkeyup='{ edit }' + ) + + tr( + each='{ backup.state.type.state === "empty" ? [] : [backup] }' + ) + td(colspan='2') + h3.text-center New cluster settings + + tr + td + | Name + td + input.form-control( + ref='name' + type='text' + placeholder='new-cluster (can be { 53 - team.length - 1 } characters long)' + title='Database cluster name, must be a valid hostname component' + pattern='[a-z0-9]+[a-z0-9\-]+[a-z0-9]+' + maxlength='{ 53 - team.length - 1 }' + required + value='{ name }' + onchange='{ nameChange }' + onkeyup='{ nameChange }' + style='width: 100%' + ) + + tr( + each='{ !["", "*"].includes(config.target_namespace) ? [] : [namespace] }' + ) + td + | Namespace + td + select.form-control( + ref='namespace' + title='Database cluster Kubernetes namespace' + onchange='{ edit }' + onkeyup='{ edit }' + style='width: 100%' + ) + option( + each='{ namespace in parent.config.namespaces }' + selected='{ state === namespace }' + value='{ namespace }' + ) + | { namespace } + + tr + td Owning team + td + select.form-control( + name='team' + onchange='{ teamChange }' + onkeyup='{ teamChange }' + ) + option( + each='{ team in teams }' + value='{ team }' + ) + | { team } + + tr + td PostgreSQL version + td + select.form-control( + name='postgresqlVersion' + onchange='{ versionChange }' + onkeyup='{ versionChange }' + ) + option( + each='{ version in opts.config.postgresql_versions }' + value='{ version }' + ) + | { version } + + tr + td DNS name: + td + | { dnsName } + + tr + td Number of instances + td + input.form-control( + ref='instanceCount' + type='number' + min='1' + required + value='{ instanceCount }' + onchange='{ instanceCountChange }' + onkeyup='{ instanceCountChange }' + style='width: 100%' + ) + + tr(if='{ [undefined, true].includes(config.master_load_balancer_visible) }') + td Master load balancer + td + label + input( + type='checkbox' + value='{ enableMasterLoadBalancer }' + onchange='{ toggleEnableMasterLoadBalancer }' + ) + | + | Enable master ELB + + tr(if='{ [undefined, true].includes(config.replica_load_balancer_visible) }') + td Replica load balancer + td + label + input( + type='checkbox' + value='{ enableReplicaLoadBalancer }' + onchange='{ toggleEnableReplicaLoadBalancer }' + ) + | + | Enable replica ELB + + tr + td Volume size + td + .input-group + input.form-control( + ref='volumeSize' + type='number' + min='1' + required + value='{ volumeSize }' + onchange='{ volumeChange }' + onkeyup='{ volumeChange }' + ) + .input-group-addon + .input-units Gi + + tr(if='{ config.users_visible }') + td + button.btn.btn-success.btn-xs(onclick='{ users.add }') + span.glyphicon.glyphicon-plus + | + | Users + td + .input-group(each='{ users.state }') + .input-group-btn + button.btn.btn-danger(onclick='{ users.remove }') + span.glyphicon.glyphicon-trash + input.username.form-control( + type='text' + placeholder='Username' + title='^[a-z0-9]([-_a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-_a-z0-9]*[a-z0-9])?)*$' + pattern='^[a-z0-9]([-_a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-_a-z0-9]*[a-z0-9])?)*$' + required + value='{ state }' + onchange='{ edit }' + onkeyup='{ edit }' + style='width: 100%' + ) + + tr(if='{ config.databases_visible }') + td + button.btn.btn-success.btn-xs(onclick='{ databases.add }') + span.glyphicon.glyphicon-plus + | + | Databases + td + .input-group(each='{ databases.state }') + .input-group-btn + button.btn.btn-danger( + onclick='{ databases.remove }' + style='float: right' + ) + span.glyphicon.glyphicon-trash + input.databasename.form-control( + type='text' + placeholder='Database name' + title='Alphanumerics or underscore; may not start on a digit' + pattern='^[a-zA-Z_][a-zA-Z0-9_]*$' + required + value='{ state }' + onchange='{ edit }' + onkeyup='{ edit }' + style='width: 50%' + ) + select.owner.form-control( + onchange='{ owner.edit }' + onkeyup='{ owner.edit }' + disabled='{ users.valids.length === 0 }' + style='width: 50%' + ) + option( + selected='{ owner.state.length === 0 }' + value='' + disabled + hidden + ) + | Select owner… + option( + each='{ users.valids }' + selected='{ state === parent.owner.state }' + value='{ state }' + ) + | { state } + + tr(if='{ !jQuery.isEmptyObject(config.static_network_whitelist) }') + td Allowed IP ranges + td + ul.ips + li(each='{ value, name in config.static_network_whitelist }') + label + input( + type='checkbox' + value='{ name }' + onchange='{ toggleIPRange }' + checked='{ name in ranges }' + ) + | + | { name } + + tr(if='{ config.nat_gateways_visible }') + td + button.btn.btn-success.btn-xs(onclick='{ nats.add }') + span.glyphicon.glyphicon-plus + | + | AWS NAT IPs + td + div( + each='{ nats.state }' + ) + .input-group + .input-group-btn + button.btn.btn-danger(onclick='{ nats.remove }') + span.glyphicon.glyphicon-trash + input.form-control( + type='text' + required + value='{ state }' + onchange='{ edit }' + onkeyup='{ edit }' + ) + .input-group-addon + .input-units / 32 + + tr(if='{ config.odd_host_visible }') + td Odd host + td + .input-group + input.form-control( + ref='odd' + type='text' + name='odd' + placeholder='IP' + onchange='{ oddChanges }' + onkeyup='{ oddChanges }' + value='{ odd }' + ) + .input-group-addon + .input-units / 32 + + tr(if='{ config.resources_visible }') + td Resources + td + table(width='100%') + + tr + td CPU + td + + .input-group + .input-group-addon.resource-type Request + input.form-control( + ref='cpuRequest' + type='number' + placeholder='{ cpu.state.request.initialValue }' + min='1' + required + value='{ cpu.state.request.state }' + onchange='{ cpu.state.request.edit }' + onkeyup='{ cpu.state.request.edit }' + ) + .input-group-addon + .input-units m + + .input-group + .input-group-addon.resource-type Limit + input.form-control( + ref='cpuLimit' + type='number' + placeholder='{ cpu.state.limit.initialValue }' + min='1' + required + value='{ cpu.state.limit.state }' + onchange='{ cpu.state.limit.edit }' + onkeyup='{ cpu.state.limit.edit }' + ) + .input-group-addon + .input-units m + + tr + td Memory + td + + .input-group + .input-group-addon.resource-type Request + input.form-control( + ref='memoryRequest' + type='number' + placeholder='{ memory.state.request.initialValue }' + min='1' + required + value='{ memory.state.request.state }' + onchange='{ memory.state.request.edit }' + onkeyup='{ memory.state.request.edit }' + ) + .input-group-addon + .input-units Gi + + .input-group + .input-group-addon.resource-type Limit + input.form-control( + ref='memoryLimit' + type='number' + placeholder='{ memory.state.limit.initialValue }' + min='1' + required + value='{ memory.state.limit.state }' + onchange='{ memory.state.limit.edit }' + onkeyup='{ memory.state.limit.edit }' + ) + .input-group-addon + .input-units Gi + + .col-lg-3 + help-general(config='{ opts.config }') + + script. + + // Pass a refresh callback for this tag to the options constructor argument + // used for all Dynamic objects built in this tag: + const add_refresh = object => Object.assign( + {}, + object, + { refresh: () => this.update() } + ) + const Dynamic = options => this.opts.opts.Dynamic(add_refresh(options)) + const Dynamics = options => this.opts.opts.Dynamics(add_refresh(options)) + const DynamicSet = (items, options) => ( + this.opts.opts.DynamicSet(items, add_refresh(options)) + ) + + const dedent = require('dedent-js') + + // Dedent twice because pug adds tabs: + this.yamlTemplate = dedent` + kind: "postgresql" + apiVersion: "acid.zalan.do/v1" + + metadata: + name: "{{ team }}-{{ name }}" + namespace: "{{ namespace.state }}" + labels: + team: {{ team }} + + spec: + teamId: "{{ team }}" + postgresql: + version: "{{ postgresqlVersion }}" + numberOfInstances: {{ instanceCount }} + {{#if enableMasterLoadBalancer}} + enableMasterLoadBalancer: true + {{/if}} + {{#if enableReplicaLoadBalancer}} + enableReplicaLoadBalancer: true + {{/if}} + volume: + size: "{{ volumeSize }}Gi" + {{#if users}} + users:{{#each users}} + {{ state }}: []{{/each}}{{/if}} + {{#if databases}} + databases:{{#each databases}} + {{ state }}: {{ owner.state }}{{/each}}{{/if}} + allowedSourceRanges: + # IP ranges to access your cluster go here + {{#each ranges}} # {{ @key }} + {{#each this }} + - {{ this }} + {{/each}} + {{/each}}{{#if nats}} + # NAT instances + {{#each nats}} + - {{ state }}/32 + {{/each}}{{/if}}{{#if odd}} + # Your odd host IP + - {{ odd }}/32 + {{/if}} + + resources: + requests: + cpu: {{ cpu.state.request.state }}m + memory: {{ memory.state.request.state }}Gi + limits: + cpu: {{ cpu.state.limit.state }}m + memory: {{ memory.state.limit.state }}Gi{{#if restoring}} + + clone: + cluster: "{{ backup.state.name.state }}" + uid: "{{ backup.state.uid.state }}"{{#if pitr}} + timestamp: "{{ backup.state.timestamp.state }}"{{/if}}{{/if}} + ` + + const yamlParser = require('js-yaml') + this.teams = this.opts.teams + this.config = this.opts.config + + this.getContext = () => { + return { + name: this.name.toLowerCase(), + team: this.team.toLowerCase(), + postgresqlVersion: this.postgresqlVersion, + instanceCount: this.instanceCount, + enableMasterLoadBalancer: this.enableMasterLoadBalancer, + enableReplicaLoadBalancer: this.enableReplicaLoadBalancer, + volumeSize: this.volumeSize, + users: this.users.valids, + databases: this.databases.valids, + ranges: this.ranges, + nats: this.nats.valids, + odd: this.odd, + cpu: this.cpu, + memory: this.memory, + backup: this.backup, + namespace: this.namespace, + restoring: this.backup.state.type.state !== 'empty', + pitr: this.backup.state.type.state === 'pitr', + } + } + + this.getYAML = () => { + yaml = Handlebars.compile(this.yamlTemplate) + yaml = yaml(this.getContext()) + return yaml + } + + this.on('update', () => { + this.teams = this.opts.teams + this.config = this.opts.config + + yaml = Handlebars.compile(this.yamlTemplate) + yaml = yaml(this.getContext()) + + this.refs.yamlNice.innerHTML = yaml + + Prism.highlightAll() + }) + + this.oddChanges = e => { + this.odd = e.target.value + } + + this.ranges = {} + + this.toggleIPRange = e => { + if (e.target.checked) { + this.ranges[e.target.value] = this.config.static_network_whitelist[e.target.value] + } else { + delete this.ranges[e.target.value] + } + this.update() + } + + this.toggleEnableMasterLoadBalancer = e => { + this.enableMasterLoadBalancer = !this.enableMasterLoadBalancer + } + + this.toggleEnableReplicaLoadBalancer = e => { + this.enableReplicaLoadBalancer = !this.enableReplicaLoadBalancer + } + + this.volumeChange = e => { + this.volumeSize = +e.target.value + } + + this.updateDNSName = () => { + this.dnsName = this.config.dns_format_string.format( + this.name, + this.team, + this.namespace.state, + ) + } + + this.updateClusterName = () => { + this.clusterName = (this.team + '-' + this.name).toLowerCase() + this.checkClusterExists() + this.updateDNSName() + } + + this.nameChange = e => { + this.name = e.target.value.toLowerCase() + this.updateClusterName() + } + + this.teamChange = e => { + this.team = e.target.value.toLowerCase() + this.updateClusterName() + } + + this.instanceCountChange = e => { + this.instanceCount = +e.target.value + } + + this.checkClusterExists = () => ( + jQuery + .get( + '/postgresqls/' + + this.namespace.state + + '/' + + this.clusterName + ) + .done(() => this.clusterExists = true) + .fail(() => this.clusterExists = false) + .always(() => this.update()) + ) + + this.versionChange = e => { + this.postgresqlVersion = e.target.value + } + + this.requestCreate = e => { + jsonPayload = JSON.stringify(yamlParser.safeLoad(this.getYAML())) + + this.creating = true + this.update() + + jQuery.ajax({ + type: 'POST', + url: '/create-cluster', + contentType:'application/json', + data: jsonPayload, + processData: false, + success: () => { + route( + '/status/' + + encodeURI(this.namespace.state) + + '/' + + encodeURI(this.clusterName) + ) + }, + error: (r, status, error) => { + console.log('Create request failed') + }, + dataType: 'json' + }) + } + + const clipboard = new Clipboard('.btn-copy', { + text: () => { return this.getYAML() } + }) + + this.namespace = Dynamic({ + init: () => ( + !this.config.target_namespace + || ['', '*'].includes(this.config.target_namespace) + ? 'default' + : this.config.target_namespace + ), + }) + ;{ + const update_namespace = this.namespace.update + this.namespace.update = value => { + update_namespace(value) + this.updateClusterName() + this.update() + } + } + + this.nats = Dynamics() + + this.users = Dynamics() + this.users.itemInit = () => Dynamic({ + update: (value, instance, event) => { + if (value === '' || instance.state.length > 0) { + this.databases.state.forEach(database => { + if (database.owner.state === instance.state) { + database.owner.update(value, database.owner, event) + } + }) + } + instance.state = value + this.update() + // Note: there is a bug here somewhere. + // + // To reproduce it: + // 1. Add user user1 + // 2. Add user user2 + // 3. Add user user22 + // 4. Add database 1 + // 5. Add database 2 + // 6. Set database 1 owner to user2 + // 7. Set database 2 owner to user22 + // 8. Type another 2 at the end of the username for user2, so that it becomes a repeated copy of user22. It should automatically set the database 1 owner to user22. + // 9. Delete the character you just typed, so that the first user22, which used to be user2 before the last step, becomes user2 again. + // + // In principle, that last step should set the owners for both database 1 and database 2 to user2, as both databases had owner user22 which was edited. Instead, the owner for database 1 becomes user1 (WTF???) and the owner for database 2 becomes user2 (which is correct). Note that the tag states are updated correctly, and the HTML shown in the Chrome element inspector has the selected attribute in the correct option elements — yet the wrong owner option is selected in the rendered select control on the page for the owner of database 1. + // + // Solution attempted that failed because riotjs does weird things with the DOM: + // + // $('select.owner', this.root).each(select => { + // var options = $(select).children() + // for (var i = 0; i < options.length; i++) { + // const option = options[i] + // const selected = $(option).attr('selected') + // if (selected === 'true' || selected === 'selected') { + // select.selectedIndex = i + // child.selected = true + // } else { + // child.selected = false + // } + // } + // }) + // + // Hours wasted on this stupid bug: 3 + // Please keep this counter updated. + } + }) + + { + const baseRemoveUser = this.users.remove + this.users.remove = event => { + this.databases.state.forEach(database => { + if (database.owner.state === event.item.state) { + database.owner.update('', database.owner, event) + } + }) + baseRemoveUser(event) + this.update() + } + } + + this.databases = Dynamics({ + itemInit: () => { + const item = Dynamic() + const baseItemUpdate = item.update + item.update = (value, instance, event) => { + this.check() + baseItemUpdate(value, instance, event) + } + item.owner = Dynamic({ + validState: username => ( + this.users.valids.find(user => user.state === username) !== undefined + ) + }) + return item + }, + itemValid: item => item.valid() && item.owner.valid(), + }) + + const DynamicResource = options => { + const instance = DynamicSet({ + request: () => options.request, + limit: () => options.limit, + }) + instance.state.request.initialValue = options.request + instance.state.limit.initialValue = options.limit + return instance + } + + this.cpu = DynamicResource({ request: 100, limit: 1000 }) + this.memory = DynamicResource({ request: 1, limit: 1 }) + + this.backup = DynamicSet({ + type: () => 'empty', + name: () => '', + uid: () => '', + timestamp: () => '', + }) + + const pitr_timestamp_format = 'YYYY-MM-DDTHH:mm:ss.SSSZ' + this.backup.state.timestamp.validState = ( + base => ( + state => { + if (!base(state)) { + return false + } + const parsed = moment.utc(state, pitr_timestamp_format, true) + return moment.isMoment(parsed) && !isNaN(parsed.utcOffset()) + } + ) + )(this.backup.state.timestamp.validState) + + this.allValid = () => ( + !this.creating && + this.refs.name && this.refs.name.validity.valid && + this.refs.instanceCount && this.refs.instanceCount.validity.valid && Number.isInteger(+this.instanceCount) && + this.refs.volumeSize && this.refs.volumeSize.validity.valid && Number.isInteger(+this.volumeSize) && + this.refs.cpuRequest && this.refs.cpuRequest.validity.valid && Number.isInteger(+this.cpu.state.request.state) && + this.refs.cpuLimit && this.refs.cpuLimit.validity.valid && Number.isInteger(+this.cpu.state.limit.state) && + this.refs.memoryRequest && this.refs.memoryRequest.validity.valid && Number.isInteger(+this.memory.state.request.state) && + this.refs.memoryLimit && this.refs.memoryLimit.validity.valid && Number.isInteger(+this.memory.state.limit.state) && + [ + this.users, + this.databases, + this.backup, + ].every(x => x.valid()) + ) + + this.check = () => { + const setValidity = validity => (_, element) => ( + element.setCustomValidity( + validity(element) + ) + ) + + $('select.owner', this.root).each(setValidity(owner => + owner.value ? '' : 'Must select an owner for this database' + )) + + const counts = (values, key=x => x) => values.map(key).reduce( + (o, item) => { + if (item in o) { o[item] += 1 } + else { o[item] = 1 } + return o + }, + {} + ) + + $('input.timestamp', this.root).each(setValidity(timestamp => + this.backup.state.timestamp.valid() ? '' : + 'Timestamp must be formatted as ISO-8601 with milliseconds; note that a UTC offset as ±HH:mm is required: YYYY-MM-DDTHH:mm:ss.SSSZ e.g. 2018-12-31T23:59:59.999+14:00' + )) + + const userCounts = counts(this.users.state, item => item.state) + $('input.username', this.root).each(setValidity(name => + !name.value || userCounts[name.value] === 1 ? '' : + 'Usernames must not repeat' + )) + + const databaseCounts = counts(this.databases.state, item => item.state) + $('input.databasename', this.root).each(setValidity(name => + !name.value || databaseCounts[name.value] === 1 ? '' : + 'Database names must not repeat' + )) + + if (this.refs.submitbutton) { + this.refs.submitbutton.disabled = ( + $(document.querySelectorAll(':invalid')).length !== 0 + ) || $('select.owner:disabled').length !== 0 + } + } + + this.on('updated', this.check) + + this.reset_form = () => { + if (this.pollProgressTimer) { + clearInterval(this.pollProgressTimer) + } + + this.creating = false + this.name = '' + + if (this.teams && this.teams.length > 0) { + this.team = this.teams[0] + } else { + this.team = '' + } + + this.clusterName = (this.name + '-' + this.team).toLowerCase() + this.volumeSize = 10 + this.instanceCount = 1 + this.ranges = {} + this.odd = '' + this.enableMasterLoadBalancer = false + this.enableReplicaLoadBalancer = false + + this.postgresqlVersion = this.postgresqlVersion = ( + this.config.postgresql_versions[0] + ) + + this.updateDNSName(); + + this.check() + + this.backup.state.type.update( + this.opts.backup_name === undefined + ? 'empty' + : this.opts.backup_timestamp === undefined + ? 'restore' + : 'pitr' + ) + + this.backup.state.name.update( + decodeURI(this.opts.backup_name || '') + ) + + this.backup.state.uid.update( + !this.opts.backup_uid || this.opts.backup_uid === 'base' + ? '' + : this.opts.backup_uid + ) + + this.backup.state.timestamp.update( + !this.opts.backup_timestamp + ? '' + : moment.utc( + decodeURI(this.opts.backup_timestamp) + ).format(pitr_timestamp_format) + ) + + this.opts && this.opts.backup_name && delete this.opts.backup_name + this.opts && this.opts.backup_uid && delete this.opts.backup_uid + this.opts && this.opts.backup_timestamp && delete this.opts.backup_timestamp + + this.update() + } + + this.on('mount', this.reset_form) diff --git a/ui/app/src/postgresql.tag.pug b/ui/app/src/postgresql.tag.pug new file mode 100644 index 000000000..88e5e130b --- /dev/null +++ b/ui/app/src/postgresql.tag.pug @@ -0,0 +1,219 @@ +postgresql + .container-fluid + + h1.page-header + nav(aria-label='breadcrumb') + ol.breadcrumb + + li.breadcrumb-item + a(href='/#/list') + | PostgreSQL clusters + + li.breadcrumb-item(if='{ cluster_path }') + a(href='/#/status/{ cluster_path }') + | { qname } + + .row(if='{ cluster_path }') + + .col-lg-3 + h2 Cluster YAML definition + + div(if='{ progress.postgresql }') +
+ + pre(if='{ !progress.postgresql }') + code # Loading + + virtual(if='{ uid }') + h3 Cluster UID + code { uid } + + .col-lg-6 + + div + + .btn-toolbar.pull-right + .btn-group( + aria-label='Actions' + role='group' + ) + + a.btn.btn-info( + href='/#/logs/{ cluster_path }' + ) + | Logs + + a.btn( + class='btn-{ opts.read_write ? "primary" : "info" }' + if='{ progress.postgresql }' + href='/#/clone/{ clustername }/{ uid }/{ encodeURI(new Date().toISOString()) }' + ) + | Clone + + a.btn( + class='btn-{ opts.read_write ? "warning" : "info" }' + href='/#/edit/{ cluster_path }' + ) + | Edit + + button.btn.btn-danger( + if='{ opts.read_write }' + onclick='{ delete_cluster }' + ) + | Delete + + h2 Checking status of cluster + + .progress + .progress-bar.progress-bar-success(style='width: 20%' if='{ progress.requestStatus === "OK" || progress.masterLabel }') + .progress-bar.progress-bar-success(style='width: 20%' if='{ progress.postgresql }') + .progress-bar.progress-bar-success(style='width: 20%' if='{ progress.statefulSet }') + .progress-bar.progress-bar-success(style='width: 20%' if='{ progress.containerFirst }') + .progress-bar.progress-bar-success(style='width: 20%' if='{ progress.masterLabel }') + .progress-bar.progress-bar-info.progress-bar-striped.active(if='{ !progress.masterLabel }' style='width: 10%') + + .alert.alert-info(if='{ !progress.requestStatus }') PostgreSQL cluster requested + .alert.alert-danger(if='{ progress.requestStatus !== "OK" }') Create request failed + .alert.alert-success(if='{ progress.requestStatus === "OK" }') Create request successful ({ new Date(progress.createdTimestamp).toLocaleString() }) + + .alert.alert-info(if='{ !progress.statefulSet }') StatefulSet pending + .alert.alert-success(if='{ progress.statefulSet }') StatefulSet created + + .alert.alert-info(if='{ progress.statefulSet && !progress.containerFirst }') Waiting for 1st container to spawn + .alert.alert-success(if='{ progress.containerFirst }') First PostgreSQL cluster container spawned + + .alert.alert-info(if='{ !progress.postgresql }') PostgreSQL cluster manifest pending + .alert.alert-success(if='{ progress.postgresql }') PostgreSQL cluster manifest created + + .alert.alert-info(if='{ progress.containerFirst && !progress.masterLabel }') Waiting for master to become available + .alert.alert-success(if='{ progress.masterLabel }') PostgreSQL master available, label is attached + .alert.alert-success(if='{ progress.masterLabel && progress.dnsName }') PostgreSQL ready: { progress.dnsName } + + .col-lg-3 + help-general(config='{ opts.config }') + + script. + + var yamlParser = require('js-yaml') + + this.delete_cluster = _ => this.parent.opts.delete_cluster( + this.namespace, + this.clustername, + ) + + this.progress = {} + this.progress.requestStatus = 'OK' + + this.pollProgressTimer = false + + this.startPollProgress = () => { + this.pollProgressTimer = setInterval(this.pollProgress, 10000) + } + + this.stopPollProgress = () => { + clearInterval(this.pollProgressTimer) + this.pollProgressTimer = false + } + + this.pollProgress = () => { + jQuery.get( + '/postgresqls/' + this.cluster_path, + ).done(data => { + this.progress.postgresql = true + this.progress.postgresqlManifest = data + this.progress.createdTimestamp = data.metadata.creationTimestamp + this.uid = this.progress.postgresqlManifest.metadata.uid + this.update() + + jQuery.get( + '/statefulsets/' + this.cluster_path, + ).done(data => { + this.progress.statefulSet = true + this.update() + + jQuery.get( + '/statefulsets/' + this.cluster_path + '/pods', + ).done(data => { + if (data.length > 0) { + this.progress.containerFirst = true + } + + masters = data.filter((x) => { return x.labels['spilo-role'] === 'master'} ) + if (masters.length === 1) { + this.progress.masterLabel = true + } + + this.update() + + jQuery.get( + '/services/' + this.cluster_path, + ).done(data => { + if (data.metadata && data.metadata.annotations && 'zalando.org/dnsname' in data.metadata.annotations) { + this.progress.dnsName = data.metadata.annotations['zalando.org/dnsname'] + } else if (data.metadata && data.metadata.annotations && 'external-dns.alpha.kubernetes.io/hostname' in data.metadata.annotations) { + this.progress.dnsName = data.metadata.annotations['external-dns.alpha.kubernetes.io/hostname'] + } else { + // Kubernetes Service name should resolve + this.progress.dnsName = data.metadata.name + '.' + data.metadata.namespace + } + + this.update() + }) + }) + }) + }) + } + + this.on('mount', () => { + this.uid = undefined + const namespace = this.namespace = this.opts.namespace + const clustername = this.clustername = this.opts.clustername + const qname = this.qname = namespace + '/' + clustername + const cluster_path = this.cluster_path = ( + encodeURI(namespace) + + '/' + encodeURI(clustername) + ) + this.stopPollProgress() + this.pollProgress() + this.startPollProgress() + }) + + this.on('unmount', () => + this.stopPollProgress() + ) + + this.on('update', () => { + if (this.progress.postgresqlManifest) { + const manifest = this.progress.postgresqlManifest + + const last_applied = 'kubectl.kubernetes.io/last-applied-configuration' + if (manifest.metadata.annotations) { + delete manifest.metadata.annotations[last_applied] + } + + delete manifest.metadata.creationTimestamp + delete manifest.metadata.deletionGracePeriodSeconds + delete manifest.metadata.deletionTimestamp + delete manifest.metadata.generation + delete manifest.metadata.resourceVersion + delete manifest.metadata.selfLink + delete manifest.metadata.uid + delete manifest.status + + if (this.refs.yamlNice) { + this.refs.yamlNice.innerHTML = yamlParser.safeDump( + this.progress.postgresqlManifest, + { + sortKeys: true, + }, + ) + } + + } else { + if(this.refs.yamlNice) { + this.refs.yamlNice.innerHTML = '# Loading postgresql cluster manifest' + } + } + + Prism.highlightAll() + }) diff --git a/ui/app/src/postgresqls.tag.pug b/ui/app/src/postgresqls.tag.pug new file mode 100644 index 000000000..41d648737 --- /dev/null +++ b/ui/app/src/postgresqls.tag.pug @@ -0,0 +1,247 @@ +postgresqls + .container-fluid + + h1.page-header + nav(aria-label='breadcrumb') + ol.breadcrumb + + li.breadcrumb-item + a(href='/#/list') + | PostgreSQL clusters + + .sk-spinner-pulse( + if='{ my_clusters !== null && other_clusters !== null && (my_clusters === undefined || other_clusters === undefined) }' + ) + + p(if='{ my_clusters === null || other_clusters === null }') + | Error loading clusters. Please + | + a(onclick='window.location.reload(true)') try again + | + | or + | + a(href='/') start over + | . + + div( + if='{ my_clusters && other_clusters }' + ) + + p + | Search: + | + input( + type='text' + onchange='{ filter.edit }' + onkeyup='{ filter.edit }' + value='{ filter.state }' + ) + + .page-header + h1 My team's clusters ({ my_clusters.length }) + + table.table.table-hover(if='{ my_clusters.length > 0 }') + + thead + tr + th(style='width: 120px') Team + th(style='width: 50px') Pods + th(style='width: 140px') CPU + th(style='width: 130px') Memory + th(style='width: 100px') Size + th(style='width: 130px') Namespace + th Name + + tbody + tr( + each='{ my_clusters }' + hidden='{ !namespaced_name.toLowerCase().includes(filter.state.toLowerCase()) }' + ) + td { team } + td { nodes } + td { cpu } / { cpu_limit } + td { memory } / { memory_limit } + td { volume_size } + + td(style='white-space: pre') + | { namespace } + + td + a( + href='/#/status/{ cluster_path(this) }' + ) + | { name } + + .btn-group.pull-right( + aria-label='Cluster { qname } actions' + role='group' + style='display: flex' + ) + + a.btn.btn-info( + href='/#/status/{ cluster_path(this) }' + ) + i.fa.fa-check-circle.regular + | Status + + a.btn.btn-info( + if='{ opts.config.pgview_link }' + href='{ opts.config.pgview_link }{ cluster_path(this) }' + ) + i.fa.fa-chart-line + | Pgview + + a.btn.btn-info( + href='/#/logs/{ cluster_path(this) }' + ) + i.fa.fa-align-justify + | Logs + + a.btn( + class='btn-{ opts.read_write ? "primary" : "info" }' + href='/#/clone/{ encodeURI(name) }/{ encodeURI(uid) }/{ encodeURI(new Date().toISOString()) }' + ) + i.fa.fa-clone.regular + | Clone + + a.btn( + class='btn-{ opts.read_write ? "warning" : "info" }' + href='/#/edit/{ cluster_path(this) }' + ) + | Edit + + button.btn.btn-danger( + if='{ opts.read_write }' + onclick='{ delete_cluster }' + ) + | Delete + + .page-header + h1 Other clusters ({ other_clusters.length}) + + table.table.table-hover(if='{ other_clusters.length > 0 }') + + thead + tr + th(style='width: 120px') Team + th(style='width: 50px') Pods + th(style='width: 140px') CPU + th(style='width: 130px') Memory + th(style='width: 100px') Size + th(style='width: 130px') Namespace + th Name + + tbody + tr( + each='{ other_clusters }' + hidden='{ !namespaced_name.toLowerCase().includes(filter.state.toLowerCase()) }' + ) + td { team } + td { nodes } + td { cpu } / { cpu_limit } + td { memory } / { memory_limit } + td { volume_size } + + td(style='white-space: pre') + | { namespace } + + td + + a( + href='/#/status/{ cluster_path(this) }' + ) + | { name } + + .btn-group.pull-right( + aria-label='Cluster { qname } actions' + role='group' + style='display: flex' + ) + + a.btn.btn-info( + href='/#/status/{ cluster_path(this) }' + ) + i.fa.fa-check-circle.regular + | Status + + a.btn.btn-info( + if='{ opts.config.pgview_link }' + href='{ opts.config.pgview_link }{ cluster_path(this) }' + target='_blank' + ) + i.fa.fa-chart-line + | Pgview + + a.btn.btn-info( + href='/#/logs/{ cluster_path(this) }' + ) + i.fa.fa-align-justify + | Logs + + a.btn( + class='btn-{ opts.read_write ? "primary" : "info" }' + href='/#/clone/{ encodeURI(name) }/{ encodeURI(uid) }/{ encodeURI(new Date().toISOString()) }' + ) + i.fa.fa-clone.regular + | Clone + + a.btn( + class='btn-{ opts.read_write ? "warning" : "info" }' + href='/#/edit/{ cluster_path(this) }' + ) + | Edit + + button.btn.btn-danger( + if='{ opts.read_write }' + onclick='{ delete_cluster }' + ) + | Delete + + script. + + // Pass a refresh callback for this tag to the options constructor argument + // used for all Dynamic objects built in this tag: + const add_refresh = object => Object.assign( + {}, + object, + { refresh: () => this.update() } + ) + const Dynamic = options => this.parent.opts.Dynamic(add_refresh(options)) + + this.filter = Dynamic() + + this.my_clusters = undefined + this.other_clusters = undefined + + this.delete_cluster = event => this.parent.opts.delete_cluster( + event.item.namespace, + event.item.name, + ) + + const cluster_path = this.cluster_path = cluster => ( + encodeURI(cluster.namespace) + + '/' + encodeURI(cluster.name) + ) + + this.on('mount', () => + jQuery + .get('/postgresqls') + .done(clusters => { + this.my_clusters = [] + this.other_clusters = [] + clusters.forEach(cluster => + ( + this.opts.teams.includes( + cluster.team.toLowerCase() + ) + ? this.my_clusters + : this.other_clusters + ).push(cluster) + ) + }) + .fail(() => { + this.my_clusters = null + this.other_clusters = null + }) + .always(() => this.update()) + ) diff --git a/ui/app/src/prism.js b/ui/app/src/prism.js new file mode 100644 index 000000000..66999f02b --- /dev/null +++ b/ui/app/src/prism.js @@ -0,0 +1,3 @@ +/* http://prismjs.com/download.html?themes=prism&languages=yaml */ +var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(\w+)\b/i,t=0,n=_self.Prism={manual:_self.Prism&&_self.Prism.manual,util:{encode:function(e){return e instanceof a?new a(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&").replace(/e.length)break e;if(!(v instanceof a)){u.lastIndex=0;var b=u.exec(v),k=1;if(!b&&h&&m!=r.length-1){if(u.lastIndex=y,b=u.exec(e),!b)break;for(var w=b.index+(c?b[1].length:0),_=b.index+b[0].length,P=m,A=y,j=r.length;j>P&&_>A;++P)A+=r[P].length,w>=A&&(++m,y=A);if(r[m]instanceof a||r[P-1].greedy)continue;k=P-m,v=e.slice(y,A),b.index-=y}if(b){c&&(f=b[1].length);var w=b.index+f,b=b[0].slice(f),_=w+b.length,x=v.slice(0,w),O=v.slice(_),S=[m,k];x&&S.push(x);var N=new a(i,g?n.tokenize(b,g):b,d,b,h);S.push(N),O&&S.push(O),Array.prototype.splice.apply(r,S)}}}}}return r},hooks:{all:{},add:function(e,t){var a=n.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var a=n.hooks.all[e];if(a&&a.length)for(var r,l=0;r=a[l++];)r(t)}}},a=n.Token=function(e,t,n,a,r){this.type=e,this.content=t,this.alias=n,this.length=0|(a||"").length,this.greedy=!!r};if(a.stringify=function(e,t,r){if("string"==typeof e)return e;if("Array"===n.util.type(e))return e.map(function(n){return a.stringify(n,t,e)}).join("");var l={type:e.type,content:a.stringify(e.content,t,r),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:r};if("comment"==l.type&&(l.attributes.spellcheck="true"),e.alias){var i="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(l.classes,i)}n.hooks.run("wrap",l);var o=Object.keys(l.attributes).map(function(e){return e+'="'+(l.attributes[e]||"").replace(/"/g,""")+'"'}).join(" ");return"<"+l.tag+' class="'+l.classes.join(" ")+'"'+(o?" "+o:"")+">"+l.content+""},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var t=JSON.parse(e.data),a=t.language,r=t.code,l=t.immediateClose;_self.postMessage(n.highlight(r,n.languages[a],a)),l&&_self.close()},!1),_self.Prism):_self.Prism;var r=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return r&&(n.filename=r.src,!document.addEventListener||n.manual||r.hasAttribute("data-manual")||("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(n.highlightAll):window.setTimeout(n.highlightAll,16):document.addEventListener("DOMContentLoaded",n.highlightAll))),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); +Prism.languages.yaml={scalar:{pattern:/([\-:]\s*(![^\s]+)?[ \t]*[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)[^\r\n]+(?:\3[^\r\n]+)*)/,lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:/(\s*(?:^|[:\-,[{\r\n?])[ \t]*(![^\s]+)?[ \t]*)[^\r\n{[\]},#\s]+?(?=\s*:\s)/,lookbehind:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:/([:\-,[{]\s*(![^\s]+)?[ \t]*)(\d{4}-\d\d?-\d\d?([tT]|[ \t]+)\d\d?:\d{2}:\d{2}(\.\d*)?[ \t]*(Z|[-+]\d\d?(:\d{2})?)?|\d{4}-\d{2}-\d{2}|\d\d?:\d{2}(:\d{2}(\.\d*)?)?)(?=[ \t]*($|,|]|}))/m,lookbehind:!0,alias:"number"},"boolean":{pattern:/([:\-,[{]\s*(![^\s]+)?[ \t]*)(true|false)[ \t]*(?=$|,|]|})/im,lookbehind:!0,alias:"important"},"null":{pattern:/([:\-,[{]\s*(![^\s]+)?[ \t]*)(null|~)[ \t]*(?=$|,|]|})/im,lookbehind:!0,alias:"important"},string:{pattern:/([:\-,[{]\s*(![^\s]+)?[ \t]*)("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')(?=[ \t]*($|,|]|}))/m,lookbehind:!0,greedy:!0},number:{pattern:/([:\-,[{]\s*(![^\s]+)?[ \t]*)[+\-]?(0x[\da-f]+|0o[0-7]+|(\d+\.?\d*|\.?\d+)(e[\+\-]?\d+)?|\.inf|\.nan)[ \t]*(?=$|,|]|})/im,lookbehind:!0},tag:/![^\s]+/,important:/[&*][\w]+/,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./}; diff --git a/ui/app/src/restore.tag.pug b/ui/app/src/restore.tag.pug new file mode 100644 index 000000000..3a322df3f --- /dev/null +++ b/ui/app/src/restore.tag.pug @@ -0,0 +1,472 @@ +restore + .container-fluid + + .sk-spinner-pulse(if='{ stored_clusters === undefined }') + + p(if='{ stored_clusters === null }') + | Error loading stored clusters. Please + | + a(onclick="window.location.reload(true);") try again + | + | or + | + a(href="/") start over + | . + + p(if='{ stored_clusters && stored_clusters.length === 0 }') + | No stored clusters found. + + div(if='{ stored_clusters && stored_clusters.length > 0 }') + + h1.page-header + nav(aria-label='breadcrumb') + ol.breadcrumb + + li.breadcrumb-item + a(href='/#/backups') + | PostgreSQL cluster backups ({ stored_clusters.length }) + + p + | Search: + | + input( + each='{ [filter] }' + type='text' + onchange='{ edit }' + onkeyup='{ edit }' + value='{ state }' + ) + + .stored-clusters.panel-group.collapsible + .stored-clusters.panel.panel-default.collapsible( + each='{ stored_clusters }' + hidden='{ !name.includes(filter.state) }' + ) + + .stored-clusters.panel-heading.collapsible( + id='{ id }-head' + class='{ collapsed ? "collapsed" : "" }' + data-toggle='collapse' + data-target='#{ id }-collapse' + ) + a.panel-title.collapsible + | { name } + + .stored-clusters.panel-collapse.collapse.collapsible( + id='{ id }-collapse' + data-stored-clusters='{ name }' + ) + .stored-clusters.panel-body.collapsible(id='{ id }-body') + + .sk-spinner-pulse(if='{ versions === undefined }') + + p(if='{ versions === null }') + | Error loading backups. Please try again or + | + a(href="/") start over + | . + + p(if='{ versions && versions.length === 0 }') + | No backups found. + + div(if='{ versions && versions.length > 0 }') + + .versions.panel-group.collapsible(style='margin-top: 0.3em') + .versions.panel.panel-default.collapsible(each='{ versions }') + + .versions.panel-heading.collapsible( + id='{ id }-head' + class='{ collapsed ? "collapsed" : "" }' + data-toggle='collapse' + data-target='#{ id }-collapse' + ) + a.versions.panel-title.collapsible + | { name } + + .versions.panel-collapse.collapse.collapsible( + id='{ id }-collapse' + data-stored-clusters='{ parent.name }' + data-versions='{ name }' + ) + + .versions.panel-body.collapsible(id='{ id }-body') + + .sk-spinner-pulse(if='{ basebackups === undefined }') + + p(if='{ basebackups === null }') + | Error loading snapshots. Please try again or + | + a(href="/") start over + | . + + p(if='{ basebackups && basebackups.length === 0 }') + | No snapshots found. + + div(if='{ basebackups && basebackups.length > 0 }') + + div( + style='margin-bottom: 0.3em' + ) + + .pull-left(style='margin-right: 0.3em') + button.btn.btn-primary.pull-left( + id='{ id }-clone' + ) + | Clone at latest state + + div + .input-group + .input-group-btn + button.btn.btn-info(id='{ id }-pitr') + | Clone at + .input.form-control(type='text') + | { clone_time && to_clone_time(clone_time) } + + .timeline(id='{ id }-timeline') + + .basebackups.panel-group.collapsible(style='margin-top: 0.3em') + .basebackups.panel.panel-default.collapsible( + each='{ basebackups }' + ) + + .basebackups.panel-heading.collapsible( + id='{ id }-head' + class='{ collapsed ? "collapsed" : "" }' + data-toggle='collapse' + data-target='#{ id }-collapse' + ) + a.basebackups.panel-title.collapsible + | { relative_time(last_modified) } + span.label.label-success( + if='{ index === basebackups.length - 1 }' + style='margin-left: 0.5em' + ) + | latest snapshot + + .basebackups.panel-collapse.collapse.collapsible( + id='{ id }-collapse' + data-stored-clusters='{ parent.parent.name }' + data-versions='{ parent.name }' + data-backups='{ name }' + ) + + .basebackups.panel-body.collapsible(id='{ id }-body') + + table.basebackups.table + + tr + th Name + td { name } + + tr + th Size + td { expanded_size_bytes } + + tr + th Last modified + td { relative_time(last_modified) } + + tr + th WAL segment backup start + td { wal_segment_backup_start } + + tr + th WAL segment backup stop + td { wal_segment_backup_stop } + + tr + th WAL segment offset backup start + td { wal_segment_offset_backup_start } + + tr + th WAL segment offset backup stop + td { wal_segment_offset_backup_stop } + + button.btn.btn-info( + id='{ id }-restore' + ) + | Clone at this snapshot + + + script. + + const sort_by = require('sort-by') + + // Pass a refresh callback for this tag to the options constructor argument + // used for all Dynamic objects built in this tag: + const add_refresh = object => Object.assign( + {}, + object, + { refresh: () => this.update() } + ) + const Dynamic = options => this.parent.opts.Dynamic(add_refresh(options)) + + const filter = this.filter = Dynamic() + + const to_timestamp = this.to_timestamp = time => ( + time + .replace('T', ' ') + .replace('.000Z', '') + ) + + const trunc_timestamp = this.trunc_timestamp = time => ( + Math.trunc(time / 1000) * 1000 + ) + + const to_clone_time = this.to_clone_time = time => to_timestamp( + new Date(trunc_timestamp(time)) + .toISOString() + ) + + const min = (a, b) => a <= b ? a : b + const max = (a, b) => a >= b ? a : b + const minimum = array => array.reduce(min) + const maximum = array => array.reduce(max) + const both = (f, g, x) => [f(x), g(x)] + + const setting = property => (object, value) => { + object[property] = value + return object + } + + const load_time = this.load_time = +new Date() + const relative_time = this.relative_time = time => { + const relative = humanizeDuration( + trunc_timestamp(load_time) + - Date.parse(time) + ) + return `${to_timestamp(time)} UTC (${relative} ago)` + } + + const q = selector => jQuery(selector, this.root) + + const route_on_click = (selector, target) => ( + q(selector).on('click', event => + route(target(event).join('/')) + ) + ) + + const on_collapse = action => ( + ({ + klass, + collection, + predicate = () => true, + selector_prefix = '', + body, + }) => ( + q(`${selector_prefix}.${klass}.collapse`) + .on( + action + '.bs.collapse', + event => { + if (!( + event.target.classList.contains(klass) + && predicate(event.target.dataset) + )) { + return true + } + const target = collection.find(item => + item.name === event.target.getAttribute('data-' + klass) + ) + target['collapsed'] = action === 'hide' + body(target) + this.update() + }, + ) + ) + ) + + const collapsible_handlers = options => { + on_collapse('hide')(Object.assign({}, options, { body: _ => {}})) + on_collapse('show')(options) + } + + const get_subresources_once = ({ + parent_resource, + key, + url, + body, + build_subresource = subresource => subresource, + build_subresources = subresources => subresources, + }) => { + if (parent_resource[key] === undefined) { + ( + jQuery + .get(url) + .done(values => { + parent_resource[key] = ( + build_subresources(values) + .map(build_subresource) + ) + this.update() + body(parent_resource[key]) + }) + .fail(() => parent_resource[key] = null) + .always(() => this.update()) + ) + } + } + + this.stored_clusters = undefined + + this.on('mount', () => + get_subresources_once({ + parent_resource: this, + key: 'stored_clusters', + url: '/stored_clusters', + build_subresource: stored_cluster_name => ({ + id: 'stored-cluster-' + stored_cluster_name, + name: stored_cluster_name, + collapsed: true, + }), + body: stored_clusters => collapsible_handlers({ + klass: 'stored-clusters', + collection: stored_clusters, + body: stored_cluster => get_subresources_once({ + parent_resource: stored_cluster, + key: 'versions', + url: '/stored_clusters/' + stored_cluster.name, + build_subresource: version_name => ({ + id: stored_cluster.id + '-version-' + version_name, + name: version_name, + collapsed: true, + stored_cluster: stored_cluster, + }), + body: versions => collapsible_handlers({ + klass: 'versions', + collection: versions, + selector_prefix: `#${stored_cluster.id}-collapse `, + predicate: data => stored_cluster.name === data.storedClusters, + body: version => get_subresources_once({ + parent_resource: version, + key: 'basebackups', + url: ( + '/stored_clusters/' + stored_cluster.name + + '/' + version.name + ), + build_subresource: basebackup => Object.assign(basebackup, { + id: version.id + '-basebackup-' + basebackup.name, + }), + build_subresources: basebackups => ( + basebackups + .sort(sort_by('last_modified')) + .map(setting('index')) + ), + body: basebackups => { + if (basebackups.length === 0) { + return + } + + const basebackup_age = basebackup => ( + +new Date(basebackup.last_modified) + ) + + const oldest = version.basebackups[0] + const newest = version.basebackups[ + version.basebackups.length - 1 + ] + const [start, end] = both( + maximum, + minimum, + [ + load_time, + ...[oldest, newest].map(basebackup_age), + ], + ) + const span = end - start + const padding_time = 0.1 * span + + basebackups.forEach(basebackup => { + route_on_click( + '#' + basebackup.id + '-restore', + () => [ + '/clone', + encodeURI(stored_cluster.name), + encodeURI(version.name), + // TODO: Ideally, this should use the basebackup's end + // LSN, not the S3 last modification timestamp. However, + // the current implementation of the clone feature does + // not allow specifying `recovery_target_lsn`. + encodeURI(basebackup.last_modified), + ], + ) + }) + + version.timeline = new vis.Timeline( + q('#' + version.id + '-timeline')[0], + new vis.DataSet([ + ...version.basebackups.map( + basebackup => ({ + id: basebackup.index, + content: ( + to_timestamp(basebackup.last_modified) + .replace(' ', '
') + + ' (UTC)' + ), + start: basebackup.last_modified, + }) + ), + { + id: version.basebackups.length, + content: 'now', + start: load_time, + type: 'point', + } + ]), + { + min: min - padding_time, + max: max + 5 * padding_time, + moment: time => vis.moment(time).utc(), + clickToUse: true, + moveable: true, + zoomable: true, + showCurrentTime: true, + } + ) + + version.clone_time = trunc_timestamp(end - span / 3) + version.timeline.addCustomTime( + version.clone_time, + 'clone_time', + ) + + version.timeline.on('timechange', properties => { + version.clone_time = +properties.time + this.update() + }) + + version.timeline.on('select', selection => + version.basebackups.forEach(basebackup => + q('#' + basebackup.id + '-collapse').collapse( + selection.items.includes(basebackup.index) + ? 'show' + : 'hide' + ) + ) + ) + + route_on_click( + '#' + version.id + '-clone', + () => [ + '/clone', + encodeURI(stored_cluster.name), + encodeURI(version.name), + encodeURI(to_clone_time(load_time)), + ], + ) + + route_on_click( + '#' + version.id + '-pitr', + () => [ + '/clone', + encodeURI(stored_cluster.name), + encodeURI(version.name), + encodeURI(to_clone_time(version.clone_time)), + ], + ) + }, + }), + }), + }), + }), + }) + ) diff --git a/ui/app/src/status.tag.pug b/ui/app/src/status.tag.pug new file mode 100644 index 000000000..eae63fbdb --- /dev/null +++ b/ui/app/src/status.tag.pug @@ -0,0 +1,149 @@ +status + .container-fluid + + h1.page-header + nav(aria-label="breadcrumb") + ol.breadcrumb + + li.breadcrumb-item + a(href='/#/operator') + | Workers + + virtual(if='{ operatorShowLogs && logs }') + li.breadcrumb-item { worker_id } + li.breadcrumb-item + a(href='/#/operator/worker/{ worker_id }/logs') + | Logs + + virtual(if='{ operatorShowQueue && queue }') + li.breadcrumb-item { worker_id } + li.breadcrumb-item + a(href='/#/operator/worker/{ worker_id }/queue') + | Queue + + div(if='{ status }') + + div(if='{ !operatorShowLogs && !operatorShowQueue }') + + table.table.table-hover + + thead + tr + td Worker ID + td Queue size + td Actions + + tr(each='{ queue_size, worker_id in status.WorkerQueueSize}') + td { worker_id } + td { queue_size } + + td + .btn-group( + aria-label="Worker { worker_id} actions" + role="group" + ) + + a.btn.btn-info( + href='/#/operator/worker/{ worker_id }/logs' + ) + | Logs + + a.btn.btn-info( + href='/#/operator/worker/{ worker_id }/queue' + ) + | Queue + + div(if='{ operatorShowLogs && logs }') + table.table.table-hover + tr(each='{ logs }') + + td + span.label.label-font-size(class='{ levels[Level].class }') + | { levels[Level].label } + + td(style='white-space: pre') + | { Time } + + td(style='white-space: pre') + | { ClusterName } + + td { Message } + + div(if='{ operatorShowQueue && queue }') + table.table.table-hover + tr(each='{ queue }') + + td(style='white-space: pre') + | { EventTime } + + td(style='white-space: pre') + | { EventType } + + script. + + this.levels = { + 0: { class: '', label: 'Panic' }, + 1: { class: '', label: 'Fatal'}, + 2: { class: 'label-danger', label: 'Error'}, + 3: { class: 'label-warning', label: 'Warning'}, + 4: { class: 'label-primary', label: 'Info'}, + 5: { class: 'label-info', label: 'Debug'}, + } + + this.status = { + 'Clusters': 0, + } + + this.pollStatus = () => { + jQuery.get( + '/operator/status', + ).done(data => { + this.update({status: data}) + }) + } + + this.logs = [] + this.queue = [] + + this.on('mount', () => { + route.exec() + }) + + this.pollLogs = id => { + jQuery.get( + '/operator/workers/' + id + '/logs', + ).done(data => { + data.reverse() + this.update({logs: data}) + }) + } + + this.pollQueue = id => { + jQuery.get( + '/operator/workers/' + id + '/queue', + ).done(data => + this.update({queue: data.List}) + ) + } + + var subRoute = route.create() + + subRoute('/operator/worker/*/logs', id => { + this.worker_id = id + this.operatorShowLogs = true + this.operatorShowQueue = false + this.pollLogs(this.worker_id) + }) + + subRoute('/operator/worker/*/queue', id => { + this.worker_id = id + this.operatorShowLogs = false + this.operatorShowQueue = true + this.pollQueue(this.worker_id) + }) + + subRoute('/operator', () => { + this.operatorShowLogs = false + this.operatorShowQueue = false + this.pollStatus() + }) diff --git a/ui/app/webpack.config.js b/ui/app/webpack.config.js new file mode 100644 index 000000000..3dbf674a1 --- /dev/null +++ b/ui/app/webpack.config.js @@ -0,0 +1,118 @@ +const DEBUG = process.env.NODE_ENV !== 'production' +const entry = ['./src/app.js'] +const path = require('path') +const pkg = require('./package.json') +const webpack = require('webpack') + +module.exports = { + context: path.join(__dirname, './'), + devtool: DEBUG ? 'inline-source-map' : false, + entry: entry, + mode: DEBUG ? 'development' : 'production', + target: 'web', + + node: { + fs: 'empty' + }, + + externals: { + '$': '$', + 'jquery': 'jQuery', + }, + + output: { + // filename: DEBUG ? 'app.js' : 'app-[hash].js' + filename: 'app.js', + library: 'App', + path: path.resolve(pkg.config.buildDir), + publicPath: DEBUG ? '/' : './', + }, + + plugins: [ + new webpack.optimize.OccurrenceOrderPlugin(), + new webpack.HotModuleReplacementPlugin(), + + new webpack.LoaderOptionsPlugin({ + debug: DEBUG, + }), + + // Print on rebuild when watching; see + // https://github.com/webpack/webpack/issues/1499#issuecomment-155064216 + function () { + this.plugin('watch-run', (watching, callback) => { + console.log('Begin compile at ' + new Date()) + callback() + }) + }, + + ], + + module: { + + rules: [ + + { + test: /\.tag\.pug$/, + loader: 'riot-tag-loader', + exclude: /node_modules/, + query: { + hot: true, + template: 'pug', + type: 'es6', + }, + }, + + { + test: /\.tag$/, + loader: 'riot-tag-loader', + exclude: /node_modules/, + query: { + hot: false, + type: 'es6', + }, + }, + + { + test: /\.js$/, + loader: 'babel-loader', + exclude: /node_modules/, + query: { + plugins: ['@babel/transform-runtime'], + presets: ['@babel/preset-env'], + }, + }, + + { + test: /\.html$/, + loader: 'file-loader?name=[path][name].[ext]', + exclude: /node_modules/, + }, + + { + test: /\.jpe?g$|\.svg$|\.png$/, + loader: 'file-loader?name=[path][name].[ext]', + exclude: /node_modules/, + }, + + { + test: /\.json$/, + loader: 'json', + exclude: /node_modules/, + }, + + { + test: /\.(otf|eot|svg|ttf|woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, + loader: 'url?limit=8192&mimetype=application/font-woff', + }, + + { + test: /\.json$/, + loader: 'json', + include: path.join(__dirname, 'node_modules', 'pixi.js'), + }, + + ], + + }, + +} diff --git a/ui/manifests/deployment.yaml b/ui/manifests/deployment.yaml new file mode 100644 index 000000000..c270cbe11 --- /dev/null +++ b/ui/manifests/deployment.yaml @@ -0,0 +1,68 @@ +apiVersion: "apps/v1" +kind: "Deployment" +metadata: + name: "postgres-operator-ui" + namespace: "default" + labels: + application: "postgres-operator-ui" + team: "acid" +spec: + replicas: 1 + selector: + matchLabels: + application: "postgres-operator-ui" + template: + metadata: + labels: + application: "postgres-operator-ui" + team: "acid" + spec: + serviceAccountName: postgres-operator-ui + containers: + - name: "service" + image: registry.opensource.zalan.do/acid/postgres-operator-ui:v1.2.0 + ports: + - containerPort: 8081 + protocol: "TCP" + readinessProbe: + httpGet: + path: "/health" + port: 8081 + initialDelaySeconds: 5 + timeoutSeconds: 1 + resources: + limits: + cpu: "300m" + memory: "3000Mi" + requests: + cpu: "100m" + memory: "100Mi" + env: + - name: "APP_URL" + value: "http://localhost:8081" + - name: "OPERATOR_API_URL" + value: "http://localhost:8080" + - name: "TARGET_NAMESPACE" + value: "default" + - name: "TEAMS" + value: |- + [ + "acid" + ] + - name: "OPERATOR_UI_CONFIG" + value: |- + { + "docs_link":"https://postgres-operator.readthedocs.io/en/latest/", + "dns_format_string": "{1}-{0}.{2}", + "databases_visible": true, + "master_load_balancer_visible": true, + "nat_gateways_visible": false, + "replica_load_balancer_visible": true, + "resources_visible": true, + "users_visible": true, + "postgresql_versions": [ + "11", + "10", + "9.6" + ] + } diff --git a/ui/manifests/ingress.yaml b/ui/manifests/ingress.yaml new file mode 100644 index 000000000..4efac53ac --- /dev/null +++ b/ui/manifests/ingress.yaml @@ -0,0 +1,15 @@ +apiVersion: "networking.k8s.io/v1beta1" +kind: "Ingress" +metadata: + name: "postgres-operator-ui" + namespace: "default" + labels: + application: "postgres-operator-ui" +spec: + rules: + - host: "ui.example.org" + http: + paths: + - backend: + serviceName: "postgres-operator-ui" + servicePort: 80 diff --git a/ui/manifests/service.yaml b/ui/manifests/service.yaml new file mode 100644 index 000000000..989ec041e --- /dev/null +++ b/ui/manifests/service.yaml @@ -0,0 +1,15 @@ +apiVersion: "v1" +kind: "Service" +metadata: + name: "postgres-operator-ui" + namespace: "default" + labels: + application: "postgres-operator-ui" +spec: + type: "ClusterIP" + selector: + application: "postgres-operator-ui" + ports: + - port: 80 + protocol: "TCP" + targetPort: 8081 diff --git a/ui/manifests/ui-service-account-rbac.yaml b/ui/manifests/ui-service-account-rbac.yaml new file mode 100644 index 000000000..4ae218e74 --- /dev/null +++ b/ui/manifests/ui-service-account-rbac.yaml @@ -0,0 +1,67 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: postgres-operator-ui + namespace: default + +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRole +metadata: + name: postgres-operator-ui +rules: +- apiGroups: + - acid.zalan.do + resources: + - postgresqls + verbs: + - create + - delete + - get + - list + - patch + - update +- apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - services + verbs: + - get + - list +- apiGroups: + - apps + resources: + - statefulsets + verbs: + - get + - list +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - list +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: postgres-operator-ui +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: postgres-operator-ui +subjects: +- kind: ServiceAccount +# note: the cluster role binding needs to be defined +# for every namespace the operator-ui service account lives in. + name: postgres-operator-ui + namespace: default diff --git a/ui/operator_ui/__init__.py b/ui/operator_ui/__init__.py new file mode 100644 index 000000000..d9f0a3893 --- /dev/null +++ b/ui/operator_ui/__init__.py @@ -0,0 +1,2 @@ +# This version is replaced during release process. +__version__ = '2017.0.dev1' diff --git a/ui/operator_ui/__main__.py b/ui/operator_ui/__main__.py new file mode 100644 index 000000000..5d6a8109e --- /dev/null +++ b/ui/operator_ui/__main__.py @@ -0,0 +1,3 @@ +from .main import main + +main() diff --git a/ui/operator_ui/backoff.py b/ui/operator_ui/backoff.py new file mode 100644 index 000000000..ad36b27ae --- /dev/null +++ b/ui/operator_ui/backoff.py @@ -0,0 +1,48 @@ +import random + + +def expo(n: int, base=2, factor=1, max_value=None): + """Exponential decay. + + Adapted from https://github.com/litl/backoff/blob/master/backoff.py (MIT License) + + Args: + base: The mathematical base of the exponentiation operation + factor: Factor to multiply the exponentation by. + max_value: The maximum value to yield. Once the value in the + true exponential sequence exceeds this, the value + of max_value will forever after be yielded. + """ + a = factor * base ** n + if max_value is None or a < max_value: + return a + else: + return max_value + + +def random_jitter(value, jitter=1): + """Jitter the value a random number of milliseconds. + + Copied from https://github.com/litl/backoff/blob/master/backoff.py (MIT License) + + This adds up to 1 second of additional time to the original value. + Prior to backoff version 1.2 this was the default jitter behavior. + Args: + value: The unadulterated backoff value. + """ + return value + random.uniform(0, jitter) + + +def full_jitter(value): + """Jitter the value across the full range (0 to value). + + Copied from https://github.com/litl/backoff/blob/master/backoff.py (MIT License) + + This corresponds to the "Full Jitter" algorithm specified in the + AWS blog's post on the performance of various jitter algorithms. + (http://www.awsarchitectureblog.com/2015/03/backoff.html) + + Args: + value: The unadulterated backoff value. + """ + return random.uniform(0, value) diff --git a/ui/operator_ui/cluster_discovery.py b/ui/operator_ui/cluster_discovery.py new file mode 100644 index 000000000..9c89735ac --- /dev/null +++ b/ui/operator_ui/cluster_discovery.py @@ -0,0 +1,125 @@ +import logging +import re +from pathlib import Path + +import kubernetes.client +import kubernetes.config +import tokens +from requests.auth import AuthBase + +# default URL points to kubectl proxy +DEFAULT_CLUSTERS = 'http://localhost:8001/' +CLUSTER_ID_INVALID_CHARS = re.compile('[^a-z0-9:-]') + +logger = logging.getLogger(__name__) + +tokens.configure(from_file_only=True) + + +def generate_cluster_id(url: str): + '''Generate some "cluster ID" from given API server URL''' + for prefix in ('https://', 'http://'): + if url.startswith(prefix): + url = url[len(prefix):] + return CLUSTER_ID_INVALID_CHARS.sub('-', url.lower()).strip('-') + + +class StaticAuthorizationHeaderAuth(AuthBase): + '''Static authentication with given "Authorization" header''' + + def __init__(self, authorization): + self.authorization = authorization + + def __call__(self, request): + request.headers['Authorization'] = self.authorization + return request + + +class OAuthTokenAuth(AuthBase): + '''Dynamic authentication using the "tokens" library to load OAuth tokens from file + (potentially mounted from a Kubernetes secret)''' + + def __init__(self, token_name): + self.token_name = token_name + tokens.manage(token_name) + + def __call__(self, request): + token = tokens.get(self.token_name) + request.headers['Authorization'] = 'Bearer {}'.format(token) + return request + + +class Cluster: + def __init__(self, id, api_server_url, ssl_ca_cert=None, auth=None, cert_file=None, key_file=None): + self.id = id + self.api_server_url = api_server_url + self.ssl_ca_cert = ssl_ca_cert + self.auth = auth + self.cert_file = cert_file + self.key_file = key_file + + +class StaticClusterDiscoverer: + + def __init__(self, api_server_urls: list): + self._clusters = [] + + if not api_server_urls: + try: + kubernetes.config.load_incluster_config() + except kubernetes.config.ConfigException: + # we are not running inside a cluster + # => assume default kubectl proxy URL + cluster = Cluster(generate_cluster_id(DEFAULT_CLUSTERS), DEFAULT_CLUSTERS) + else: + logger.info("in cluster configuration failed") + config = kubernetes.client.configuration + cluster = Cluster( + generate_cluster_id(config.host), + config.host, + ssl_ca_cert=config.ssl_ca_cert, + auth=StaticAuthorizationHeaderAuth(config.api_key['authorization'])) + self._clusters.append(cluster) + else: + for api_server_url in api_server_urls: + logger.info("api server url: {}".format(api_server_url)) + if 'localhost' not in api_server_url: + # TODO: hacky way of detecting whether we need a token or not + auth = OAuthTokenAuth('read-only') + else: + auth = None + self._clusters.append(Cluster(generate_cluster_id(api_server_url), api_server_url, auth=auth)) + + def get_clusters(self): + return self._clusters + + +class KubeconfigDiscoverer: + + def __init__(self, kubeconfig_path: Path, contexts: set): + self._path = kubeconfig_path + self._contexts = contexts + + def get_clusters(self): + # Kubernetes Python client expects "vintage" string path + config_file = str(self._path) + contexts, current_context = kubernetes.config.list_kube_config_contexts(config_file) + for context in contexts: + if self._contexts and context['name'] not in self._contexts: + # filter out + continue + config = kubernetes.client.ConfigurationObject() + kubernetes.config.load_kube_config(config_file, context=context['name'], client_configuration=config) + authorization = config.api_key.get('authorization') + if authorization: + auth = StaticAuthorizationHeaderAuth(authorization) + else: + auth = None + cluster = Cluster( + context['name'], + config.host, + ssl_ca_cert=config.ssl_ca_cert, + cert_file=config.cert_file, + key_file=config.key_file, + auth=auth) + yield cluster diff --git a/ui/operator_ui/main.py b/ui/operator_ui/main.py new file mode 100644 index 000000000..f34d16492 --- /dev/null +++ b/ui/operator_ui/main.py @@ -0,0 +1,1041 @@ +#!/usr/bin/env python3 +# pylama:ignore=E402 + +import gevent.monkey + +gevent.monkey.patch_all() + +import requests +import tokens + +from backoff import expo, on_exception +from click import ParamType, command, echo, option + +from flask import ( + Flask, + Response, + abort, + redirect, + render_template, + request, + send_from_directory, + session, +) + +from flask_oauthlib.client import OAuth +from functools import wraps +from gevent import sleep, spawn +from gevent.wsgi import WSGIServer +from jq import jq +from json import dumps, loads +from logging import DEBUG, ERROR, INFO, basicConfig, exception, getLogger +from os import getenv +from re import X, compile +from requests.exceptions import RequestException +from signal import SIGTERM, signal +from urllib.parse import urljoin + +from . import __version__ +from .cluster_discovery import DEFAULT_CLUSTERS, StaticClusterDiscoverer +from .oauth import OAuthRemoteAppWithRefresh + +from .spiloutils import ( + apply_postgresql, + create_postgresql, + read_basebackups, + read_namespaces, + read_pods, + read_postgresql, + read_postgresqls, + read_service, + read_statefulset, + read_stored_clusters, + read_versions, + remove_postgresql, +) + +from .utils import ( + const, + identity, + these, +) + + +# Disable access logs from Flask +getLogger('gevent').setLevel(ERROR) + +logger = getLogger(__name__) + +SERVER_STATUS = {'shutdown': False} + +APP_URL = getenv('APP_URL') +AUTHORIZE_URL = getenv('AUTHORIZE_URL') +SPILO_S3_BACKUP_BUCKET = getenv('SPILO_S3_BACKUP_BUCKET') +TEAM_SERVICE_URL = getenv('TEAM_SERVICE_URL') +ACCESS_TOKEN_URL = getenv('ACCESS_TOKEN_URL') +TOKENINFO_URL = getenv('OAUTH2_TOKEN_INFO_URL') + +OPERATOR_API_URL = getenv('OPERATOR_API_URL', 'http://postgres-operator') +OPERATOR_UI_CONFIG = getenv('OPERATOR_UI_CONFIG', '{}') +OPERATOR_UI_MAINTENANCE_CHECK = getenv('OPERATOR_UI_MAINTENANCE_CHECK', '{}') +READ_ONLY_MODE = getenv('READ_ONLY_MODE', False) in [True, 'true'] +SPILO_S3_BACKUP_PREFIX = getenv('SPILO_S3_BACKUP_PREFIX', 'spilo/') +SUPERUSER_TEAM = getenv('SUPERUSER_TEAM', 'acid') +TARGET_NAMESPACE = getenv('TARGET_NAMESPACE') +GOOGLE_ANALYTICS = getenv('GOOGLE_ANALYTICS', False) + +WALE_S3_ENDPOINT = getenv( + 'WALE_S3_ENDPOINT', + 'https+path://s3-eu-central-1.amazonaws.com:443', +) + +USE_AWS_INSTANCE_PROFILE = ( + getenv('USE_AWS_INSTANCE_PROFILE', 'false').lower() != 'false' +) + +tokens.configure() +tokens.manage('read-only') +tokens.start() + + +def service_auth_header(): + token = getenv('SERVICE_TOKEN') or tokens.get('read-only') + return { + 'Authorization': f'Bearer {token}', + } + + +MAX_CONTENT_LENGTH = 16 * 1024 * 1024 +app = Flask(__name__) +app.config['MAX_CONTENT_LENGTH'] = MAX_CONTENT_LENGTH + + +class WSGITransferEncodingChunked: + """Support HTTP Transfer-Encoding: chunked transfers""" + + def __init__(self, app): + self.app = app + + def __call__(self, environ, start_response): + from io import BytesIO + input = environ.get('wsgi.input') + length = environ.get('CONTENT_LENGTH', '0') + length = 0 if length == '' else int(length) + body = b'' + if length == 0: + if input is None: + return + if environ.get('HTTP_TRANSFER_ENCODING', '0') == 'chunked': + size = int(input.readline(), 16) + total_size = 0 + while size > 0: + # Validate max size to avoid DoS attacks + total_size += size + if total_size > MAX_CONTENT_LENGTH: + # Avoid DoS (using all available memory by streaming an + # infinite file) + start_response( + '413 Request Entity Too Large', + [('Content-Type', 'text/plain')], + ) + return [] + + body += input.read(size + 2) + size = int(input.readline(), 16) + + else: + body = environ['wsgi.input'].read(length) + + environ['CONTENT_LENGTH'] = str(len(body)) + environ['wsgi.input'] = BytesIO(body) + + return self.app(environ, start_response) + + +oauth = OAuth(app) + +auth = OAuthRemoteAppWithRefresh( + oauth, + 'auth', + request_token_url=None, + access_token_method='POST', + access_token_url=ACCESS_TOKEN_URL, + authorize_url=AUTHORIZE_URL, +) +oauth.remote_apps['auth'] = auth + + +def verify_token(token): + if not token: + return False + + r = requests.get(TOKENINFO_URL, headers={'Authorization': token}) + + return r.status_code == 200 + + +def authorize(f): + @wraps(f) + def wrapper(*args, **kwargs): + if AUTHORIZE_URL and 'auth_token' not in session: + return redirect(urljoin(APP_URL, '/login')) + return f(*args, **kwargs) + + return wrapper + + +def ok(body={}, status=200): + return ( + Response( + ( + dumps(body) + if isinstance(body, dict) or isinstance(body, list) + else body + ), + mimetype='application/json', + ), + status + ) + + +def fail(body={}, status=400, **kwargs): + return ( + Response( + dumps( + { + 'error': ' '.join(body.split()).format(**kwargs), + } + if isinstance(body, str) + else body, + ), + mimetype='application/json', + ), + status, + ) + + +def not_found(body={}, **kwargs): + return fail(body=body, status=404, **kwargs) + + +def respond(data, f=identity): + return ( + ok(f(data)) + if data is not None + else not_found() + ) + + +def wrong_namespace(**kwargs): + return fail( + body=f'The Kubernetes namespace must be {TARGET_NAMESPACE}', + status=403, + **kwargs + ) + + +def no_writes_when_read_only(**kwargs): + return fail( + body='UI is in read-only mode for production', + status=403, + **kwargs + ) + + +@app.route('/health') +def health(): + if SERVER_STATUS['shutdown']: + abort(503) + else: + return 'OK' + + +STATIC_HEADERS = { + 'cache-control': ', '.join([ + 'no-store', + 'no-cache', + 'must-revalidate', + 'post-check=0', + 'pre-check=0', + 'max-age=0', + ]), + 'Pragma': 'no-cache', + 'Expires': '-1', +} + + +@app.route('/css/') +@authorize +def send_css(path): + return send_from_directory('static/', path), 200, STATIC_HEADERS + + +@app.route('/js/') +@authorize +def send_js(path): + return send_from_directory('static/', path), 200, STATIC_HEADERS + + +@app.route('/') +@authorize +def index(): + return render_template('index.html', google_analytics=GOOGLE_ANALYTICS) + + +DEFAULT_UI_CONFIG = { + 'docs_link': 'https://github.com/zalando/postgres-operator', + 'odd_host_visible': True, + 'nat_gateways_visible': True, + 'users_visible': True, + 'databases_visible': True, + 'resources_visible': True, + 'postgresql_versions': ['9.6', '10', '11'], + 'dns_format_string': '{0}.{1}.{2}', + 'pgui_link': '', + 'static_network_whitelist': {}, +} + + +@app.route('/config') +@authorize +def get_config(): + config = loads(OPERATOR_UI_CONFIG) or DEFAULT_UI_CONFIG + config['read_only_mode'] = READ_ONLY_MODE + config['superuser_team'] = SUPERUSER_TEAM + config['target_namespace'] = TARGET_NAMESPACE + + config['namespaces'] = ( + [TARGET_NAMESPACE] + if TARGET_NAMESPACE not in ['', '*'] + else [ + namespace_name + for namespace in these( + read_namespaces(get_cluster()), + 'items', + ) + for namespace_name in [namespace['metadata']['name']] + if namespace_name not in [ + 'kube-public', + 'kube-system', + ] + ] + ) + + try: + + kubernetes_maintenance_check = ( + config.get('kubernetes_maintenance_check') or + loads(OPERATOR_UI_MAINTENANCE_CHECK) + ) + + if ( + kubernetes_maintenance_check and + {'url', 'query'} <= kubernetes_maintenance_check.keys() + ): + config['kubernetes_in_maintenance'] = ( + jq(kubernetes_maintenance_check['query']).transform( + requests.get( + kubernetes_maintenance_check['url'], + headers=service_auth_header(), + ).json(), + ) + ) + + except ValueError: + exception('Could not determine Kubernetes cluster status') + + return ok(config) + + +def get_teams_for_user(user_name): + if not TEAM_SERVICE_URL: + return loads(getenv('TEAMS', '[]')) + + return [ + team['id'].lower() + for team in requests.get( + TEAM_SERVICE_URL.format(user_name), + headers=service_auth_header(), + ).json() + ] + + +@app.route('/teams') +@authorize +def get_teams(): + return ok( + get_teams_for_user( + session.get('user_name', ''), + ) + ) + + +@app.route('/services//') +@authorize +def get_service(namespace: str, cluster: str): + + if TARGET_NAMESPACE not in ['', '*', namespace]: + return wrong_namespace() + + return respond( + read_service( + get_cluster(), + namespace, + cluster, + ), + ) + + +@app.route('/statefulsets//') +@authorize +def get_list_clusters(namespace: str, cluster: str): + + if TARGET_NAMESPACE not in ['', '*', namespace]: + return wrong_namespace() + + return respond( + read_statefulset( + get_cluster(), + namespace, + cluster, + ), + ) + + +@app.route('/statefulsets///pods') +@authorize +def get_list_members(namespace: str, cluster: str): + + if TARGET_NAMESPACE not in ['', '*', namespace]: + return wrong_namespace() + + return respond( + read_pods( + get_cluster(), + namespace, + cluster, + ), + lambda pods: [ + pod['metadata'] + for pod in these(pods, 'items') + ], + ) + + +@app.route('/namespaces') +@authorize +def get_namespaces(): + + if TARGET_NAMESPACE not in ['', '*']: + return ok([TARGET_NAMESPACE]) + + return respond( + read_namespaces( + get_cluster(), + ), + lambda namespaces: [ + namespace['name'] + for namespace in these(namespaces, 'items') + ], + ) + + +@app.route('/postgresqls') +@authorize +def get_postgresqls(): + postgresqls = [ + { + 'nodes': spec.get('numberOfInstances', ''), + 'memory': spec.get('resources', {}).get('requests', {}).get('memory', 0), + 'memory_limit': spec.get('resources', {}).get('limits', {}).get('memory', 0), + 'cpu': spec.get('resources', {}).get('requests', {}).get('cpu', 0), + 'cpu_limit': spec.get('resources', {}).get('limits', {}).get('cpu', 0), + 'volume_size': spec.get('volume', {}).get('size', 0), + 'team': ( + spec.get('teamId') or + metadata.get('labels', {}).get('team', '') + ), + 'namespace': namespace, + 'name': name, + 'uid': uid, + 'namespaced_name': namespace + '/' + name, + 'full_name': namespace + '/' + name + ('/' + uid if uid else ''), + } + for cluster in these( + read_postgresqls( + get_cluster(), + namespace=( + None + if TARGET_NAMESPACE in ['', '*'] + else TARGET_NAMESPACE + ), + ), + 'items', + ) + for spec in [cluster.get('spec', {}) if cluster.get('spec', {}) is not None else {"error": "Invalid spec in manifest"}] + for metadata in [cluster['metadata']] + for namespace in [metadata['namespace']] + for name in [metadata['name']] + for uid in [metadata.get('uid', '')] + ] + return respond(postgresqls) + + +# Note these are meant to be consistent with the operator backend validations; +# See https://github.com/zalando/postgres-operator/blob/master/pkg/cluster/cluster.go # noqa +VALID_SIZE = compile(r'^[1-9][0-9]{0,3}Gi$') +VALID_CLUSTER_NAME = compile(r'^[a-z0-9]+[a-z0-9\-]+[a-z0-9]+$') +VALID_DATABASE_NAME = compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$') +VALID_USERNAME = compile( + r''' + ^[a-z0-9]([-_a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-_a-z0-9]*[a-z0-9])?)*$ + ''', + X, +) + +ROLEFLAGS = ''' + SUPERUSER + INHERIT + LOGIN + NOLOGIN + CREATEROLE + CREATEDB + REPLICATION + BYPASSRLS +'''.split() + + +def namespaced(handler): + + def run(*args, **kwargs): + namespace = ( + args[1] + if len(args) >= 2 + else kwargs['namespace'] + ) + + if TARGET_NAMESPACE not in ['', '*', namespace]: + return wrong_namespace() + + return handler(*args, **kwargs) + + return run + + +def read_only(handler): + + def run(*args, **kwargs): + if READ_ONLY_MODE: + return no_writes_when_read_only() + + return handler(*args, **kwargs) + + return run + + +@app.route('/postgresqls//', methods=['POST']) +@authorize +@namespaced +def update_postgresql(namespace: str, cluster: str): + if READ_ONLY_MODE: + return no_writes_when_read_only() + + o = read_postgresql(get_cluster(), namespace, cluster) + if o is None: + return not_found() + + postgresql = request.get_json(force=True) + + teams = get_teams_for_user(session.get('user_name', '')) + logger.info(f'Changes to: {cluster} by {session.get("user_name", "local-user")}/{teams} {postgresql}') # noqa + + if SUPERUSER_TEAM and SUPERUSER_TEAM in teams: + logger.info(f'Allowing edit due to membership in superuser team {SUPERUSER_TEAM}') # noqa + elif not o['spec']['teamId'].lower() in teams: + return fail('Not a member of the owning team', status=401) + + # do spec copy 1 by 1 not to do unsupporeted changes for now + spec = {} + if 'allowedSourceRanges' in postgresql['spec']: + if not isinstance(postgresql['spec']['allowedSourceRanges'], list): + return fail('allowedSourceRanges invalid') + spec['allowedSourceRanges'] = postgresql['spec']['allowedSourceRanges'] + + if 'numberOfInstances' in postgresql['spec']: + if not isinstance(postgresql['spec']['numberOfInstances'], int): + return fail('numberOfInstances invalid') + spec['numberOfInstances'] = postgresql['spec']['numberOfInstances'] + + if ( + 'volume' in postgresql['spec'] + and 'size' in postgresql['spec']['volume'] + ): + size = str(postgresql['spec']['volume']['size']) + if not VALID_SIZE.match(size): + return fail('volume.size is invalid; should be like 123Gi') + + spec['volume'] = {'size': size} + + if 'enableReplicaLoadBalancer' in postgresql['spec']: + rlb = postgresql['spec']['enableReplicaLoadBalancer'] + if not rlb: + if 'enableReplicaLoadBalancer' in o['spec']: + del o['spec']['enableReplicaLoadBalancer'] + else: + spec['enableReplicaLoadBalancer'] = True + else: + if 'enableReplicaLoadBalancer' in o['spec']: + del o['spec']['enableReplicaLoadBalancer'] + + if 'enableMasterLoadBalancer' in postgresql['spec']: + rlb = postgresql['spec']['enableMasterLoadBalancer'] + if not rlb: + if 'enableMasterLoadBalancer' in o['spec']: + del o['spec']['enableMasterLoadBalancer'] + else: + spec['enableMasterLoadBalancer'] = True + else: + if 'enableMasterLoadBalancer' in o['spec']: + del o['spec']['enableMasterLoadBalancer'] + + if 'users' in postgresql['spec']: + spec['users'] = postgresql['spec']['users'] + + if not isinstance(postgresql['spec']['users'], dict): + return fail(''' + the "users" key must hold a key-value object mapping usernames + to a list of their role flags as simple strings. + e.g.: {{"some_username": ["createdb", "login"]}} + ''') + + for username, role_flags in postgresql['spec']['users'].items(): + + if not VALID_USERNAME.match(username): + return fail( + ''' + no match for valid username pattern {VALID_USERNAME} in + invalid username {username} + ''', + VALID_USERNAME=VALID_USERNAME.pattern, + username=username, + ) + + if not isinstance(role_flags, list): + return fail( + ''' + the value for the user key {username} must be a list of + the user's permissions as simple strings. + e.g.: ["createdb", "login"] + ''', + username=username, + ) + + for role_flag in role_flags: + + if not isinstance(role_flag, str): + return fail( + ''' + the value for the user key {username} must be a + list of the user's permissions as simple strings. + e.g.: ["createdb", "login"] + ''', + username=username, + ) + + if role_flag.upper() not in ROLEFLAGS: + return fail( + ''' + user {username} has invalid role flag {role_flag} + - allowed flags are {all_flags} + ''', + username=username, + role_flag=role_flag, + all_flags=', '.join(ROLEFLAGS), + ) + + if 'databases' in postgresql['spec']: + spec['databases'] = postgresql['spec']['databases'] + + if not isinstance(postgresql['spec']['databases'], dict): + return fail(''' + the "databases" key must hold a key-value object mapping + database names to their respective owner's username as a simple + string. e.g. {{"some_database_name": "some_username"}} + ''') + + for database_name, owner_username in ( + postgresql['spec']['databases'].items() + ): + + if not VALID_DATABASE_NAME.match(database_name): + return fail( + ''' + no match for valid database name pattern + {VALID_DATABASE_NAME} in invalid database name + {database_name} + ''', + VALID_DATABASE_NAME=VALID_DATABASE_NAME.pattern, + database_name=database_name, + ) + + if not isinstance(owner_username, str): + return fail( + ''' + the value for the database key {database_name} must be + the owning user's username as a simple string. e.g.: + "some_username" + ''', + database_name=database_name, + ) + + if not VALID_USERNAME.match(owner_username): + return fail( + ''' + no match for valid username pattern {VALID_USERNAME} in + invalid database owner username {owner_username} + ''', + VALID_USERNAME=VALID_USERNAME.pattern, + owner_username=owner_username, + ) + + o['spec'].update(spec) + + apply_postgresql(get_cluster(), namespace, cluster, o) + + return ok(o) + + +@app.route('/postgresqls//', methods=['GET']) +@authorize +def get_postgresql(namespace: str, cluster: str): + + if TARGET_NAMESPACE not in ['', '*', namespace]: + return wrong_namespace() + + return respond( + read_postgresql( + get_cluster(), + namespace, + cluster, + ), + ) + + +@app.route('/stored_clusters') +@authorize +def get_stored_clusters(): + return respond( + read_stored_clusters( + bucket=SPILO_S3_BACKUP_BUCKET, + prefix=SPILO_S3_BACKUP_PREFIX, + ) + ) + + +@app.route('/stored_clusters/', methods=['GET']) +@authorize +def get_versions(pg_cluster: str): + return respond( + read_versions( + bucket=SPILO_S3_BACKUP_BUCKET, + pg_cluster=pg_cluster, + prefix=SPILO_S3_BACKUP_PREFIX, + s3_endpoint=WALE_S3_ENDPOINT, + use_aws_instance_profile=USE_AWS_INSTANCE_PROFILE, + ), + ) + + + +@app.route('/stored_clusters//', methods=['GET']) +@authorize +def get_basebackups(pg_cluster: str, uid: str): + return respond( + read_basebackups( + bucket=SPILO_S3_BACKUP_BUCKET, + pg_cluster=pg_cluster, + prefix=SPILO_S3_BACKUP_PREFIX, + s3_endpoint=WALE_S3_ENDPOINT, + uid=uid, + use_aws_instance_profile=USE_AWS_INSTANCE_PROFILE, + ), + ) + + +@app.route('/create-cluster', methods=['POST']) +@authorize +def create_new_cluster(): + + if READ_ONLY_MODE: + return no_writes_when_read_only() + + postgresql = request.get_json(force=True) + + cluster_name = postgresql['metadata']['name'] + if not VALID_CLUSTER_NAME.match(cluster_name): + return fail(r'metadata.name is invalid. [a-z0-9\-]+') + + namespace = postgresql['metadata']['namespace'] + if not namespace: + return fail('metadata.namespace must not be empty') + if TARGET_NAMESPACE not in ['', '*', namespace]: + return wrong_namespace() + + teams = get_teams_for_user(session.get('user_name', '')) + logger.info(f'Create cluster by {session.get("user_name", "local-user")}/{teams} {postgresql}') # noqa + + if SUPERUSER_TEAM and SUPERUSER_TEAM in teams: + logger.info(f'Allowing create due to membership in superuser team {SUPERUSER_TEAM}') # noqa + elif not postgresql['spec']['teamId'].lower() in teams: + return fail('Not a member of the owning team', status=401) + + r = create_postgresql(get_cluster(), namespace, postgresql) + return ok() if r else fail(status=500) + + +@app.route('/postgresqls//', methods=['DELETE']) +@authorize +def delete_postgresql(namespace: str, cluster: str): + if TARGET_NAMESPACE not in ['', '*', namespace]: + return wrong_namespace() + + if READ_ONLY_MODE: + return no_writes_when_read_only() + + postgresql = read_postgresql(get_cluster(), namespace, cluster) + if postgresql is None: + return not_found() + + teams = get_teams_for_user(session.get('user_name', '')) + + logger.info(f'Delete cluster: {cluster} by {session.get("user_name", "local-user")}/{teams}') # noqa + + if SUPERUSER_TEAM and SUPERUSER_TEAM in teams: + logger.info(f'Allowing delete due to membership in superuser team {SUPERUSER_TEAM}') # noqa + elif not postgresql['spec']['teamId'].lower() in teams: + return fail('Not a member of the owning team', status=401) + + return respond( + remove_postgresql( + get_cluster(), + namespace, + cluster, + ), + const(None), + ) + + +def proxy_operator(url: str): + response = requests.get(OPERATOR_API_URL + url) + response.raise_for_status() + return respond(response.json()) + + +@app.route('/operator/status') +@authorize +def get_operator_status(): + return proxy_operator('/status/') + + +@app.route('/operator/workers//queue') +@authorize +def get_operator_get_queue(worker: int): + return proxy_operator(f'/workers/{worker}/queue') + + +@app.route('/operator/workers//logs') +@authorize +def get_operator_get_logs(worker: int): + return proxy_operator(f'/workers/{worker}/logs') + + +@app.route('/operator/clusters///logs') +@authorize +def get_operator_get_logs_per_cluster(namespace: str, cluster: str): + team, clustername = cluster.split('-', 1) + return proxy_operator(f'/clusters/{team}/{namespace}/{clustername}/logs/') + + +@app.route('/login') +def login(): + redirect = request.args.get('redirect', False) + if not redirect: + return render_template('login-deeplink.html') + + redirect_uri = urljoin(APP_URL, '/login/authorized') + return auth.authorize(callback=redirect_uri) + + +@app.route('/logout') +def logout(): + session.pop('auth_token', None) + return redirect(urljoin(APP_URL, '/')) + + +@app.route('/favicon.png') +def favicon(): + return send_from_directory('static/', 'favicon-96x96.png'), 200 + + +@app.route('/login/authorized') +def authorized(): + resp = auth.authorized_response() + if resp is None: + return 'Access denied: reason=%s error=%s' % ( + request.args['error'], + request.args['error_description'] + ) + + if not isinstance(resp, dict): + return 'Invalid auth response' + + session['auth_token'] = (resp['access_token'], '') + + r = requests.get( + TOKENINFO_URL, + headers={ + 'Authorization': f'Bearer {session["auth_token"][0]}', + }, + ) + session['user_name'] = r.json().get('uid') + + logger.info(f'Login from: {session["user_name"]}') + + # return redirect(urljoin(APP_URL, '/')) + return render_template('login-resolve-deeplink.html') + + +def shutdown(): + # just wait some time to give Kubernetes time to update endpoints + # this requires changing the readinessProbe's + # PeriodSeconds and FailureThreshold appropriately + # see https://godoc.org/k8s.io/kubernetes/pkg/api/v1#Probe + sleep(10) + exit(0) + + +def exit_gracefully(signum, frame): + logger.info('Received TERM signal, shutting down..') + SERVER_STATUS['shutdown'] = True + spawn(shutdown) + + +def print_version(ctx, param, value): + if not value or ctx.resilient_parsing: + return + echo(f'PostgreSQL Operator UI {__version__}') + ctx.exit() + + +class CommaSeparatedValues(ParamType): + name = 'comma_separated_values' + + def convert(self, value, param, ctx): + return ( + filter(None, value.split(',')) + if isinstance(value, str) + else value + ) + + +CLUSTER = None + + +def get_cluster(): + return CLUSTER + + +def set_cluster(c): + global CLUSTER + CLUSTER = c + return CLUSTER + + +def init_cluster(): + discoverer = StaticClusterDiscoverer([]) + set_cluster(discoverer.get_clusters()[0]) + + +@command(context_settings={'help_option_names': ['-h', '--help']}) +@option( + '-V', + '--version', + callback=print_version, + expose_value=False, + help='Print the current version number and exit.', + is_eager=True, + is_flag=True, +) +@option( + '-p', + '--port', + default=8081, + envvar='SERVER_PORT', + help='HTTP port to listen on (default: 8081)', + type=int, +) +@option( + '-d', + '--debug', + help='Verbose logging', + is_flag=True, +) +@option( + '--secret-key', + default='development', + envvar='SECRET_KEY', + help='Secret key for session cookies', +) +@option( + '--clusters', + envvar='CLUSTERS', + help=f'Comma separated list of Kubernetes API server URLs (default: {DEFAULT_CLUSTERS})', # noqa + type=CommaSeparatedValues(), +) +def main(port, secret_key, debug, clusters: list): + global TARGET_NAMESPACE + + basicConfig(level=DEBUG if debug else INFO) + + init_cluster() + + logger.info(f'Access token URL: {ACCESS_TOKEN_URL}') + logger.info(f'App URL: {APP_URL}') + logger.info(f'Authorize URL: {AUTHORIZE_URL}') + logger.info(f'Operator API URL: {OPERATOR_API_URL}') + logger.info(f'Readonly mode: {"enabled" if READ_ONLY_MODE else "disabled"}') # noqa + logger.info(f'Spilo S3 backup bucket: {SPILO_S3_BACKUP_BUCKET}') + logger.info(f'Spilo S3 backup prefix: {SPILO_S3_BACKUP_PREFIX}') + logger.info(f'Superuser team: {SUPERUSER_TEAM}') + logger.info(f'Target namespace: {TARGET_NAMESPACE}') + logger.info(f'Teamservice URL: {TEAM_SERVICE_URL}') + logger.info(f'Tokeninfo URL: {TOKENINFO_URL}') + logger.info(f'Use AWS instance_profile: {USE_AWS_INSTANCE_PROFILE}') + logger.info(f'WAL-E S3 endpoint: {WALE_S3_ENDPOINT}') + + if TARGET_NAMESPACE is None: + @on_exception( + expo, + RequestException, + ) + def get_target_namespace(): + logger.info('Fetching target namespace from Operator API') + return ( + requests + .get(OPERATOR_API_URL + '/config/') + .json() + ['operator'] + ['WatchedNamespace'] + ) + TARGET_NAMESPACE = get_target_namespace() + logger.info(f'Target namespace set to: {TARGET_NAMESPACE or "*"}') + + app.debug = debug + app.secret_key = secret_key + + signal(SIGTERM, exit_gracefully) + + app.wsgi_app = WSGITransferEncodingChunked(app.wsgi_app) + http_server = WSGIServer(('0.0.0.0', port), app) + logger.info(f'Listening on :{port}') + http_server.serve_forever() diff --git a/ui/operator_ui/mock.py b/ui/operator_ui/mock.py new file mode 100644 index 000000000..65066c373 --- /dev/null +++ b/ui/operator_ui/mock.py @@ -0,0 +1,12 @@ +import time +import json +import request + + +class MockCluster: + + def get_pods(self): + return [{"name": "cluster-1-XFF", "role": "master", "ip": "localhost", "port": "8080"}, + {"name": "cluster-1-XFE", "role": "replica", "ip": "localhost", "port": "8080"}, + {"name": "cluster-1-XFS", "role": "replica", "ip": "localhost", "port": "8080"}, + {"name": "cluster-2-SJE", "role": "master", "ip": "localhost", "port": "8080"}] diff --git a/ui/operator_ui/oauth.py b/ui/operator_ui/oauth.py new file mode 100644 index 000000000..34c07fd4f --- /dev/null +++ b/ui/operator_ui/oauth.py @@ -0,0 +1,32 @@ +import os + +from flask_oauthlib.client import OAuthRemoteApp + + +CREDENTIALS_DIR = os.getenv('CREDENTIALS_DIR', '') + + +class OAuthRemoteAppWithRefresh(OAuthRemoteApp): + '''Same as flask_oauthlib.client.OAuthRemoteApp, but always loads client credentials from file.''' + + def __init__(self, oauth, name, **kwargs): + # constructor expects some values, so make it happy.. + kwargs['consumer_key'] = 'not-needed-here' + kwargs['consumer_secret'] = 'not-needed-here' + OAuthRemoteApp.__init__(self, oauth, name, **kwargs) + + def refresh_credentials(self): + with open(os.path.join(CREDENTIALS_DIR, 'authcode-client-id')) as fd: + self._consumer_key = fd.read().strip() + with open(os.path.join(CREDENTIALS_DIR, 'authcode-client-secret')) as fd: + self._consumer_secret = fd.read().strip() + + @property + def consumer_key(self): + self.refresh_credentials() + return self._consumer_key + + @property + def consumer_secrect(self): + self.refresh_credentials() + return self._consumer_secret diff --git a/ui/operator_ui/spiloutils.py b/ui/operator_ui/spiloutils.py new file mode 100644 index 000000000..a707ed732 --- /dev/null +++ b/ui/operator_ui/spiloutils.py @@ -0,0 +1,312 @@ +from boto3 import client +from datetime import datetime, timezone +from furl import furl +from json import dumps +from logging import getLogger +from os import environ +from requests import Session +from urllib.parse import urljoin +from uuid import UUID +from wal_e.cmd import configure_backup_cxt + +from .utils import Attrs, defaulting, these + + +logger = getLogger(__name__) + +session = Session() + + +def request(cluster, path, **kwargs): + if 'timeout' not in kwargs: + # sane default timeout + kwargs['timeout'] = (5, 15) + if cluster.cert_file and cluster.key_file: + kwargs['cert'] = (cluster.cert_file, cluster.key_file) + + return session.get( + urljoin(cluster.api_server_url, path), + auth=cluster.auth, + verify=cluster.ssl_ca_cert, + **kwargs + ) + + +def request_post(cluster, path, data, **kwargs): + if 'timeout' not in kwargs: + # sane default timeout + kwargs['timeout'] = 5 + if cluster.cert_file and cluster.key_file: + kwargs['cert'] = (cluster.cert_file, cluster.key_file) + + return session.post( + urljoin(cluster.api_server_url, path), + data=data, + auth=cluster.auth, + verify=cluster.ssl_ca_cert, + **kwargs + ) + + +def request_put(cluster, path, data, **kwargs): + if 'timeout' not in kwargs: + # sane default timeout + kwargs['timeout'] = 5 + if cluster.cert_file and cluster.key_file: + kwargs['cert'] = (cluster.cert_file, cluster.key_file) + + return session.put( + urljoin(cluster.api_server_url, path), + data=data, + auth=cluster.auth, + verify=cluster.ssl_ca_cert, + **kwargs + ) + + +def request_delete(cluster, path, **kwargs): + if 'timeout' not in kwargs: + # sane default timeout + kwargs['timeout'] = 5 + if cluster.cert_file and cluster.key_file: + kwargs['cert'] = (cluster.cert_file, cluster.key_file) + + return session.delete( + urljoin(cluster.api_server_url, path), + auth=cluster.auth, + verify=cluster.ssl_ca_cert, + **kwargs + ) + + +def resource_api_version(resource_type): + return { + 'postgresqls': 'apis/acid.zalan.do/v1', + 'statefulsets': 'apis/apps/v1beta1', + }.get(resource_type, 'api/v1') + + +def encode_labels(label_selector): + return ','.join([ + f'{label}={value}' + for label, value in label_selector.items() + ]) + + +def kubernetes_url( + resource_type, + namespace='default', + resource_name=None, + label_selector=None, +): + + return furl('/').add( + path=( + resource_api_version(resource_type).split('/') + + ( + ['namespaces', namespace] + if namespace + else [] + ) + + [resource_type] + + ( + [resource_name] + if resource_name + else [] + ) + ), + args=( + {'labelSelector': encode_labels(label_selector)} + if label_selector + else {} + ), + ).url + + +def kubernetes_get(cluster, **kwargs): + response = request(cluster, kubernetes_url(**kwargs)) + if response.status_code == 404: + return None + if response.status_code >= 400: + response.raise_for_status() + return response.json() + + +def read_pods(cluster, namespace, spilo_cluster): + return kubernetes_get( + cluster=cluster, + resource_type='pods', + namespace=namespace, + label_selector={'version': spilo_cluster}, + ) + + +def read_pod(cluster, namespace, resource_name): + return kubernetes_get( + cluster=cluster, + resource_type='pods', + namespace=namespace, + resource_name=resource_name, + label_selector={'application': 'spilo'}, + ) + + +def read_service(cluster, namespace, resource_name): + return kubernetes_get( + cluster=cluster, + resource_type='services', + namespace=namespace, + resource_name=resource_name, + label_selector={'application': 'spilo'}, + ) + + +def read_statefulset(cluster, namespace, resource_name): + return kubernetes_get( + cluster=cluster, + resource_type='statefulsets', + namespace=namespace, + resource_name=resource_name, + label_selector={'application': 'spilo'}, + ) + + +def read_postgresql(cluster, namespace, resource_name): + return kubernetes_get( + cluster=cluster, + resource_type='postgresqls', + namespace=namespace, + resource_name=resource_name, + ) + + +def read_postgresqls(cluster, namespace): + return kubernetes_get( + cluster=cluster, + resource_type='postgresqls', + namespace=namespace, + ) + + +def read_namespaces(cluster): + return kubernetes_get( + cluster=cluster, + resource_type='namespaces', + namespace=None, + ) + + +def create_postgresql(cluster, namespace, definition): + path = kubernetes_url( + resource_type='postgresqls', + namespace=namespace, + ) + try: + r = request_post(cluster, path, dumps(definition)) + r.raise_for_status() + return True + except Exception as ex: + logger.exception("K8S create request failed") + return False + + +def apply_postgresql(cluster, namespace, resource_name, definition): + path = kubernetes_url( + resource_type='postgresqls', + namespace=namespace, + resource_name=resource_name, + ) + try: + r = request_put(cluster, path, dumps(definition)) + r.raise_for_status() + return True + except Exception as ex: + logger.exception("K8S create request failed") + return False + + +def remove_postgresql(cluster, namespace, resource_name): + path = kubernetes_url( + resource_type='postgresqls', + namespace=namespace, + resource_name=resource_name, + ) + try: + r = request_delete(cluster, path) + r.raise_for_status() + return True + except Exception as ex: + logger.exception("K8S delete request failed") + return False + + +def read_stored_clusters(bucket, prefix, delimiter='/'): + return [ + prefix['Prefix'].split('/')[-2] + for prefix in these( + client('s3').list_objects( + Bucket=bucket, + Delimiter=delimiter, + Prefix=prefix, + ), + 'CommonPrefixes', + ) + ] + + +def read_versions( + pg_cluster, + bucket, + s3_endpoint, + prefix, + delimiter='/', + use_aws_instance_profile=False, +): + return [ + 'base' if uid == 'wal' else uid + for prefix in these( + client('s3').list_objects( + Bucket=bucket, + Delimiter=delimiter, + Prefix=prefix + pg_cluster + delimiter, + ), + 'CommonPrefixes', + ) + + for uid in [prefix['Prefix'].split('/')[-2]] + + if uid == 'wal' or defaulting(lambda: UUID(uid)) + ] + + +def read_basebackups( + pg_cluster, + uid, + bucket, + s3_endpoint, + prefix, + delimiter='/', + use_aws_instance_profile=False, +): + environ['WALE_S3_ENDPOINT'] = s3_endpoint + suffix = '' if uid == 'base' else '/' + uid + return [ + { + key: value + for key, value in basebackup.__dict__.items() + if isinstance(value, str) or isinstance(value, int) + } + for basebackup in Attrs.call( + f=configure_backup_cxt, + aws_instance_profile=use_aws_instance_profile, + s3_prefix=f's3://{bucket}/{prefix}{pg_cluster}{suffix}/wal/', + )._backup_list(detail=True) + ] + + +def parse_time(s: str): + return ( + datetime.strptime(s, '%Y-%m-%dT%H:%M:%SZ') + .replace(tzinfo=timezone.utc) + .timestamp() + ) diff --git a/ui/operator_ui/static/favicon-96x96.png b/ui/operator_ui/static/favicon-96x96.png new file mode 100644 index 000000000..3c21d45b7 Binary files /dev/null and b/ui/operator_ui/static/favicon-96x96.png differ diff --git a/ui/operator_ui/static/prism.css b/ui/operator_ui/static/prism.css new file mode 100644 index 000000000..5ed523713 --- /dev/null +++ b/ui/operator_ui/static/prism.css @@ -0,0 +1,139 @@ +/* http://prismjs.com/download.html?themes=prism&languages=yaml */ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ + +code[class*="language-"], +pre[class*="language-"] { + color: black; + background: none; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { + text-shadow: none; + background: #b3d4fc; +} + +pre[class*="language-"]::selection, pre[class*="language-"] ::selection, +code[class*="language-"]::selection, code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .1em; + border-radius: .3em; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #a67f59; + background: hsla(0, 0%, 100%, .5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + +.token.function { + color: #DD4A68; +} + +.token.regex, +.token.important, +.token.variable { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} + diff --git a/ui/operator_ui/static/prism.js b/ui/operator_ui/static/prism.js new file mode 100644 index 000000000..66999f02b --- /dev/null +++ b/ui/operator_ui/static/prism.js @@ -0,0 +1,3 @@ +/* http://prismjs.com/download.html?themes=prism&languages=yaml */ +var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(\w+)\b/i,t=0,n=_self.Prism={manual:_self.Prism&&_self.Prism.manual,util:{encode:function(e){return e instanceof a?new a(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&").replace(/e.length)break e;if(!(v instanceof a)){u.lastIndex=0;var b=u.exec(v),k=1;if(!b&&h&&m!=r.length-1){if(u.lastIndex=y,b=u.exec(e),!b)break;for(var w=b.index+(c?b[1].length:0),_=b.index+b[0].length,P=m,A=y,j=r.length;j>P&&_>A;++P)A+=r[P].length,w>=A&&(++m,y=A);if(r[m]instanceof a||r[P-1].greedy)continue;k=P-m,v=e.slice(y,A),b.index-=y}if(b){c&&(f=b[1].length);var w=b.index+f,b=b[0].slice(f),_=w+b.length,x=v.slice(0,w),O=v.slice(_),S=[m,k];x&&S.push(x);var N=new a(i,g?n.tokenize(b,g):b,d,b,h);S.push(N),O&&S.push(O),Array.prototype.splice.apply(r,S)}}}}}return r},hooks:{all:{},add:function(e,t){var a=n.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var a=n.hooks.all[e];if(a&&a.length)for(var r,l=0;r=a[l++];)r(t)}}},a=n.Token=function(e,t,n,a,r){this.type=e,this.content=t,this.alias=n,this.length=0|(a||"").length,this.greedy=!!r};if(a.stringify=function(e,t,r){if("string"==typeof e)return e;if("Array"===n.util.type(e))return e.map(function(n){return a.stringify(n,t,e)}).join("");var l={type:e.type,content:a.stringify(e.content,t,r),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:r};if("comment"==l.type&&(l.attributes.spellcheck="true"),e.alias){var i="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(l.classes,i)}n.hooks.run("wrap",l);var o=Object.keys(l.attributes).map(function(e){return e+'="'+(l.attributes[e]||"").replace(/"/g,""")+'"'}).join(" ");return"<"+l.tag+' class="'+l.classes.join(" ")+'"'+(o?" "+o:"")+">"+l.content+""},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var t=JSON.parse(e.data),a=t.language,r=t.code,l=t.immediateClose;_self.postMessage(n.highlight(r,n.languages[a],a)),l&&_self.close()},!1),_self.Prism):_self.Prism;var r=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return r&&(n.filename=r.src,!document.addEventListener||n.manual||r.hasAttribute("data-manual")||("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(n.highlightAll):window.setTimeout(n.highlightAll,16):document.addEventListener("DOMContentLoaded",n.highlightAll))),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); +Prism.languages.yaml={scalar:{pattern:/([\-:]\s*(![^\s]+)?[ \t]*[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)[^\r\n]+(?:\3[^\r\n]+)*)/,lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:/(\s*(?:^|[:\-,[{\r\n?])[ \t]*(![^\s]+)?[ \t]*)[^\r\n{[\]},#\s]+?(?=\s*:\s)/,lookbehind:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:/([:\-,[{]\s*(![^\s]+)?[ \t]*)(\d{4}-\d\d?-\d\d?([tT]|[ \t]+)\d\d?:\d{2}:\d{2}(\.\d*)?[ \t]*(Z|[-+]\d\d?(:\d{2})?)?|\d{4}-\d{2}-\d{2}|\d\d?:\d{2}(:\d{2}(\.\d*)?)?)(?=[ \t]*($|,|]|}))/m,lookbehind:!0,alias:"number"},"boolean":{pattern:/([:\-,[{]\s*(![^\s]+)?[ \t]*)(true|false)[ \t]*(?=$|,|]|})/im,lookbehind:!0,alias:"important"},"null":{pattern:/([:\-,[{]\s*(![^\s]+)?[ \t]*)(null|~)[ \t]*(?=$|,|]|})/im,lookbehind:!0,alias:"important"},string:{pattern:/([:\-,[{]\s*(![^\s]+)?[ \t]*)("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')(?=[ \t]*($|,|]|}))/m,lookbehind:!0,greedy:!0},number:{pattern:/([:\-,[{]\s*(![^\s]+)?[ \t]*)[+\-]?(0x[\da-f]+|0o[0-7]+|(\d+\.?\d*|\.?\d+)(e[\+\-]?\d+)?|\.inf|\.nan)[ \t]*(?=$|,|]|})/im,lookbehind:!0},tag:/![^\s]+/,important:/[&*][\w]+/,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./}; diff --git a/ui/operator_ui/static/styles.css b/ui/operator_ui/static/styles.css new file mode 100644 index 000000000..3f05cb290 --- /dev/null +++ b/ui/operator_ui/static/styles.css @@ -0,0 +1,66 @@ +body { + padding-top: 70px; +} + +h1, h2, h3 { + font-family: 'Open Sans', sans-serif; +} + +.font-robot { + font-family: 'Roboto 300', sans-serif; +} + +input:invalid { + color: red; + font-weight: 600; +} + +ul.ips { list-style-type: none; margin: 0; padding: 0; overflow-x: hidden; } +ul.ips li { margin: 0; padding: 0; } +ul.ips label { margin: 0; padding: 0; } + +.panel-heading.collapsible { + cursor: pointer; +} + +.timeline { + cursor: pointer; +} + +.panel-heading .collapsible:after { + color: grey; + content: "\e113"; + float: right; + font-family: 'Glyphicons Halflings'; + transition: all 0.5s; +} + +.panel-heading.collapsed .collapsible:after { + transform: rotate(-180deg); +} + +:not(form):invalid,select.owner:disabled { + border: 1px solid red; + box-shadow: 0 0 10px red; +} + +.page-header { + margin-top: 0px; +} + +.page-header h1 { + margin-top: 0px; +} + +label { + font-weight: normal; + margin-top: 0; +} + +.sk-spinner-pulse { + background-color: darkblue; +} + +td { + vertical-align: middle !important; +} diff --git a/ui/operator_ui/templates/index.html b/ui/operator_ui/templates/index.html new file mode 100644 index 000000000..6b4689079 --- /dev/null +++ b/ui/operator_ui/templates/index.html @@ -0,0 +1,187 @@ + + + + + PostgreSQL Operator UI + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% if google_analytics %} + + + + {% endif %} + + + diff --git a/ui/operator_ui/templates/login-deeplink.html b/ui/operator_ui/templates/login-deeplink.html new file mode 100644 index 000000000..875b8d055 --- /dev/null +++ b/ui/operator_ui/templates/login-deeplink.html @@ -0,0 +1,13 @@ + + + Storing client location ... + + + + + \ No newline at end of file diff --git a/ui/operator_ui/templates/login-resolve-deeplink.html b/ui/operator_ui/templates/login-resolve-deeplink.html new file mode 100644 index 000000000..fac96b265 --- /dev/null +++ b/ui/operator_ui/templates/login-resolve-deeplink.html @@ -0,0 +1,18 @@ + + + Restoring client location ... + + + + + diff --git a/ui/operator_ui/update.py b/ui/operator_ui/update.py new file mode 100644 index 000000000..d65dcd1be --- /dev/null +++ b/ui/operator_ui/update.py @@ -0,0 +1,12 @@ +import logging +import time + +import gevent +import json_delta +import requests.exceptions + +from .backoff import expo, random_jitter +from .utils import get_short_error_message + +logger = logging.getLogger(__name__) + diff --git a/ui/operator_ui/utils.py b/ui/operator_ui/utils.py new file mode 100644 index 000000000..db13054f6 --- /dev/null +++ b/ui/operator_ui/utils.py @@ -0,0 +1,146 @@ +from requests.exceptions import ConnectionError, RequestException + + +class Attrs: + + @classmethod + def call(cls, f, **kwargs): + return f(cls(**kwargs)) + + def __init__(self, **kwargs): + self.attrs = kwargs + + def __getattr__(self, attr): + return self.attrs.get(attr) + + +def get_short_error_message(e: Exception): + '''Generate a reasonable short message why the HTTP request failed''' + + if isinstance(e, RequestException) and e.response is not None: + # e.g. "401 Unauthorized" + return '{} {}'.format(e.response.status_code, e.response.reason) + elif isinstance(e, ConnectionError): + # e.g. "ConnectionError" or "ConnectTimeout" + return e.__class__.__name__ + else: + return str(e) + + +def identity(value, *_, **__): + """ + Trivial identity function: return the value passed in its first argument. + + Examples: + + >>> identity(42) + 42 + + >>> list( + ... filter( + ... identity, + ... [None, False, True, 0, 1, list(), set(), dict()], + ... ), + ... ) + [True, 1] + """ + + return value + + +def const(value, *_, **__): + """ + Given a value, returns a function that simply returns that value. + + Example: + >>> f = const(42) + >>> f() + 42 + >>> f() + 42 + """ + + return lambda *_, **__: value + + +def catching(computation, catcher=identity, exception=Exception): + """ + Catch exceptions. + + Call the provided computation with no arguments. If it throws an exception + of the provided exception class (or any exception, if no class is provided), + return the result of calling the catcher function with the exception as the + sole argument. If no catcher function is specified, return the exception. + + Examples: + + Catch a KeyError and return the exception itself: + >>> catching(lambda: {'foo': 'bar'}['meh']) + KeyError('meh',) + + Catch a KeyError and return a default value: + >>> catching( + ... computation=lambda: {'foo': 'bar'}['meh'], + ... catcher=const('nope'), + ... ) + 'nope' + """ + + try: + return computation() + except exception as e: + return catcher(e) + + +def defaulting(computation, default=None, exception=Exception): + """ + Like `catching`, but just return a default value if an exception is caught. + + If no default value is supplied, default to None. + + Examples: + + Catch a KeyError and return a default value, like the `get` method: + >>> defaulting(lambda: {'foo': 'bar'}['meh'], 'nope') + 'nope' + + Turn a ZeroDivisionError into None: + >>> defaulting(lambda: 1/0) == None + True + """ + + return catching( + computation=computation, + catcher=const(default), + exception=exception, + ) + + +def these(what, where=None): + """ + Combinator for yielding multiple values with property access. + + Yields from the values generated by an attribute of the given object, or + the values generated by the given object itself if no attribute key is + specified. + + Examples: + + No attribute key specified; yields from the given object: + >>> these(['foo', 'bar']) + ['foo', 'bar'] + + An attribute key is specified; yields from the values generated by the + specified attribute's value: + object: + >>> these({'foo': ['bar', 'baz']}, 'foo') + ['bar', 'baz'] + + An invalid attribute key is specified; yields nothing: + >>> these({'foo': ['bar', 'baz']}, 'meh') + [] + """ + + if not where: + return what + return what[where] if where in what else [] diff --git a/ui/requirements.txt b/ui/requirements.txt new file mode 100644 index 000000000..f9bfdcfa0 --- /dev/null +++ b/ui/requirements.txt @@ -0,0 +1,14 @@ +Flask-OAuthlib==0.9.5 +Flask==1.0.2 +backoff==1.5.0 +boto3==1.5.14 +boto==2.48.0 +click==6.7 +furl==1.0.1 +gevent==1.2.2 +jq==0.1.6 +json_delta>=2.0 +kubernetes==3.0.0 +requests==2.20.1 +stups-tokens>=1.1.19 +wal_e==1.1.0 \ No newline at end of file diff --git a/ui/run_local.sh b/ui/run_local.sh new file mode 100755 index 000000000..2951fe049 --- /dev/null +++ b/ui/run_local.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash +set -e + +# NOTE: You still need to start the frontend bits of this application separately +# as starting it here as a child process would leave leftover processes on +# script termination; it appears there is some complication that does not allow +# the shell to clean up nodejs grandchild processes correctly upon script exit. + + +# Static bits: +export APP_URL="${API_URL-http://localhost:8081}" +export OPERATOR_API_URL="${OPERATOR_API_URL-http://localhost:8080}" +export TARGET_NAMESPACE="${TARGET_NAMESPACE-*}" + +default_operator_ui_config='{ + "docs_link":"https://postgres-operator.readthedocs.io/en/latest/", + "dns_format_string": "{1}-{0}.{2}", + "databases_visible": true, + "nat_gateways_visible": false, + "resources_visible": true, + "users_visible": true, + "postgresql_versions": [ + "11", + "10", + "9.6" + ], + "static_network_whitelist": { + "localhost": ["172.0.0.1/32"] + } +}' +export OPERATOR_UI_CONFIG="${OPERATOR_UI_CONFIG-${default_operator_ui_config}}" + +# S3 backup bucket: +export SPILO_S3_BACKUP_BUCKET="postgres-backup" + +# defines teams +teams='["acid"]' +export TEAMS="${TEAMS-${teams}}" + +# Kubernetes API URL (e.g. minikube): +kubernetes_api_url="https://192.168.99.100:8443" + +# Enable job control: +set -m + +# Clean up child processes on exit: +trap 'kill $(jobs -p)' EXIT + + +# PostgreSQL Operator UI application name as deployed: +operator_ui_application='postgres-operator-ui' + + +# Hijack the PostgreSQL Operator UI pod as a proxy for its AWS instance profile +# on the pod's localhost:1234 which allows the WAL-E code to list backups there: +kubectl exec \ + "$( + kubectl get pods \ + --server="${kubernetes_api_url}" \ + --selector="application=${operator_ui_application}" \ + --output='name' \ + | head --lines=1 \ + | sed 's@^[^/]*/@@' + )" \ + -- \ + sh -c ' + apk add --no-cache socat; + pkill socat; + socat -v TCP-LISTEN:1234,reuseaddr,fork,su=nobody TCP:169.254.169.254:80 + ' \ + & + + +# Forward localhost:1234 to localhost:1234 on the PostgreSQL Operator UI pod to +# get to the AWS instance metadata endpoint: +echo "Port forwarding to the PostgreSQL Operator UI's instance metadata service" +kubectl port-forward \ + --server="${kubernetes_api_url}" \ + "$( + kubectl get pods \ + --server="${kubernetes_api_url}" \ + --selector="application=${operator_ui_application}" \ + --output='name' \ + | head --lines=1 \ + | sed 's@^[^/]*/@@' + )" \ + 1234 \ + & + + +# Forward localhost:8080 to localhost:8080 on the PostgreSQL Operator pod, which +# allows access to the Operator REST API +# when using helm chart use --selector='app.kubernetes.io/name=postgres-operator' +echo 'Port forwarding to the PostgreSQL Operator REST API' +kubectl port-forward \ + --server="${kubernetes_api_url}" \ + "$( + kubectl get pods \ + --server="${kubernetes_api_url}" \ + --selector='name=postgres-operator' \ + --output='name' \ + | head --lines=1 \ + | sed 's@^[^/]*/@@' + )" \ + 8080 \ + & + + +# Start a local proxy on localhost:8001 of the target Kubernetes cluster's API: +kubectl proxy & + + +# Start application: +python3 \ + -m operator_ui \ + --clusters='localhost:8001' \ + $@ diff --git a/ui/setup.py b/ui/setup.py new file mode 100644 index 000000000..95ddfe182 --- /dev/null +++ b/ui/setup.py @@ -0,0 +1,78 @@ +import sys + +from setuptools import find_packages, setup +from setuptools.command.test import test as TestCommand + +from pathlib import Path + + +def read_version(package): + with (Path(package) / '__init__.py').open() as fd: + for line in fd: + if line.startswith('__version__ = '): + return line.split()[-1].strip().strip("'") + + +version = read_version('operator_ui') + + +class PyTest(TestCommand): + + user_options = [('cov-html=', None, 'Generate junit html report')] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.cov = None + self.pytest_args = ['--cov', 'operator_ui', '--cov-report', 'term-missing', '-v'] + self.cov_html = False + + def finalize_options(self): + TestCommand.finalize_options(self) + if self.cov_html: + self.pytest_args.extend(['--cov-report', 'html']) + self.pytest_args.extend(['tests']) + + def run_tests(self): + import pytest + + errno = pytest.main(self.pytest_args) + sys.exit(errno) + + +def readme(): + return open('README.rst', encoding='utf-8').read() + + +tests_require = [ + 'pytest', + 'pytest-cov' +] + +setup( + name='operator-ui', + packages=find_packages(), + version=version, + description='PostgreSQL Kubernetes Operator UI', + long_description=readme(), + author='team-acid@zalando.de', + url='https://github.com/postgres-operator', + keywords='PostgreSQL Kubernetes Operator UI', + license='MIT', + tests_require=tests_require, + extras_require={'tests': tests_require}, + cmdclass={'test': PyTest}, + test_suite='tests', + classifiers=[ + 'Development Status :: 3', + 'Intended Audience :: Developers', + 'Intended Audience :: System Administrators', + 'License :: OSI Approved :: MIT', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3.5', + 'Topic :: System :: Clustering', + 'Topic :: System :: Monitoring', + ], + include_package_data=True, # needed to include JavaScript (see MANIFEST.in) + entry_points={'console_scripts': ['operator-ui = operator_ui.main:main']} +) diff --git a/ui/tox.ini b/ui/tox.ini new file mode 100644 index 000000000..0c1c14aaf --- /dev/null +++ b/ui/tox.ini @@ -0,0 +1,27 @@ +[tox] +envlist=py35,flake8,eslint + +[tox:travis] +3.5=py35,flake8,eslint + +[testenv] +deps=pytest +commands= + pip install -r requirements.txt + python setup.py test + +[testenv:flake8] +deps=flake8 +commands=python setup.py flake8 + +[testenv:eslint] +whitelist_externals=eslint +changedir=app +commands=eslint src + +[flake8] +max-line-length=160 +ignore=E402 + +[pylama] +ignore=E402