From d9a1dce7fa5c7ace412caa0f401cc489276317cb Mon Sep 17 00:00:00 2001 From: Aditya Sharad Date: Tue, 12 Nov 2019 11:48:25 -0800 Subject: [PATCH] CodeQL for VS Code: Initial commit. --- .editorconfig | 6 + .gitattributes | 14 + .github/workflows/main.yml | 73 + .github/workflows/release.yml | 90 + .gitignore | 19 + .vscode/extensions.json | 10 + .vscode/launch.json | 84 + .vscode/settings.json | 14 + .vscode/tasks.json | 105 + CODE_OF_CONDUCT.md | 76 + CONTRIBUTING.md | 135 + LICENSE.md | 20 + README.md | 29 + build/README.md | 12 + build/package-lock.json | 1294 ++++ build/package.json | 17 + common/config/LICENSE | 28 + common/config/rush/.npmrc | 12 + common/config/rush/command-line.json | 32 + common/config/rush/common-versions.json | 43 + common/config/rush/pnpmfile.js | 32 + common/config/rush/shrinkwrap.yaml | 6646 +++++++++++++++++ common/config/rush/version-policies.json | 6 + common/scripts/install-run-rush.js | 52 + common/scripts/install-run.js | 399 + .../typescript-config/common.tsconfig.json | 28 + .../typescript-config/extension.tsconfig.json | 8 + configs/typescript-config/lib.tsconfig.json | 4 + configs/typescript-config/package.json | 18 + extensions/ql-vscode/.vscodeignore | 16 + extensions/ql-vscode/README.md | 98 + extensions/ql-vscode/gulpfile.js/index.js | 19 + .../ql-vscode/gulpfile.js/webpack.config.ts | 61 + extensions/ql-vscode/gulpfile.js/webpack.ts | 27 + .../ql-vscode/language-configuration.json | 72 + .../media/VS-marketplace-CodeQL-icon.png | Bin 0 -> 508839 bytes extensions/ql-vscode/media/black-plus.svg | 58 + .../ql-vscode/media/check-dark-mode.svg | 56 + .../ql-vscode/media/check-light-mode.svg | 57 + extensions/ql-vscode/media/logo.svg | 14 + extensions/ql-vscode/media/red-x.svg | 58 + extensions/ql-vscode/media/white-plus.svg | 58 + extensions/ql-vscode/package.json | 344 + .../src/archive-filesystem-provider.ts | 308 + extensions/ql-vscode/src/blob.d.ts | 11 + extensions/ql-vscode/src/cli-version.ts | 96 + extensions/ql-vscode/src/cli.ts | 496 ++ extensions/ql-vscode/src/config.ts | 188 + extensions/ql-vscode/src/databases-ui.ts | 263 + extensions/ql-vscode/src/databases.ts | 631 ++ extensions/ql-vscode/src/distribution.ts | 677 ++ extensions/ql-vscode/src/extension.ts | 288 + extensions/ql-vscode/src/helpers-pure.ts | 23 + extensions/ql-vscode/src/helpers.ts | 136 + extensions/ql-vscode/src/ide-server.ts | 26 + extensions/ql-vscode/src/interface-types.ts | 103 + extensions/ql-vscode/src/interface.ts | 477 ++ extensions/ql-vscode/src/logging.ts | 40 + extensions/ql-vscode/src/messages.ts | 918 +++ extensions/ql-vscode/src/queries.ts | 663 ++ extensions/ql-vscode/src/query-history.ts | 182 + .../ql-vscode/src/queryserver-client.ts | 163 + extensions/ql-vscode/src/view/LICENSE | 398 + extensions/ql-vscode/src/view/alert-table.tsx | 349 + extensions/ql-vscode/src/view/octicons.tsx | 22 + .../ql-vscode/src/view/raw-results-table.tsx | 118 + .../ql-vscode/src/view/result-table-utils.tsx | 70 + .../ql-vscode/src/view/result-tables.tsx | 147 + extensions/ql-vscode/src/view/results.tsx | 329 + extensions/ql-vscode/src/view/resultsView.css | 140 + extensions/ql-vscode/src/view/tsconfig.json | 23 + .../src/vscode-tests/index-template.ts | 61 + .../minimal-workspace/activation.test.ts | 26 + .../vscode-tests/minimal-workspace/index.ts | 4 + .../no-workspace/activation.test.ts | 13 + .../archive-filesystem-provider.test.ts | 38 + .../no-workspace/cli-version.test.ts | 50 + .../single_file.zip | Bin 0 -> 394 bytes .../no-workspace/distribution.test.ts | 178 + .../src/vscode-tests/no-workspace/index.ts | 4 + .../no-workspace/webview-uri.test.ts | 34 + .../src/vscode-tests/run-integration-tests.ts | 44 + .../syntaxes/dbscheme.tmLanguage.yml | 353 + .../ql-vscode/syntaxes/ql.tmLanguage.yml | 1006 +++ extensions/ql-vscode/test/.gitignore | 2 + .../test/data/multiple-result-sets.ql | 4 + extensions/ql-vscode/test/data/query.ql | 1 + extensions/ql-vscode/test/data/test.dbscheme | 0 .../ql-vscode/test/data/test.dbscheme.stats | 4 + .../test/pure-tests/location.test.ts | 32 + .../ql-vscode/test/pure-tests/query-test.ts | 227 + extensions/ql-vscode/tsconfig.json | 3 + lib/semmle-bqrs/gulpfile.js/index.js | 7 + lib/semmle-bqrs/package.json | 32 + lib/semmle-bqrs/src/bqrs-custom.ts | 407 + lib/semmle-bqrs/src/bqrs-file.ts | 191 + lib/semmle-bqrs/src/bqrs-parse.ts | 202 + lib/semmle-bqrs/src/bqrs-results.ts | 100 + lib/semmle-bqrs/src/bqrs-schema.ts | 66 + lib/semmle-bqrs/src/bqrs.ts | 18 + lib/semmle-bqrs/src/index.ts | 7 + .../src/path-problem-query-results.ts | 49 + lib/semmle-bqrs/src/problem-query-results.ts | 24 + lib/semmle-bqrs/tsconfig.json | 3 + lib/semmle-io-node/gulpfile.js/index.js | 7 + lib/semmle-io-node/package.json | 32 + lib/semmle-io-node/src/file-reader.ts | 66 + lib/semmle-io-node/src/index.ts | 1 + lib/semmle-io-node/tsconfig.json | 3 + lib/semmle-io/gulpfile.js/index.js | 7 + lib/semmle-io/package.json | 30 + lib/semmle-io/src/digester.ts | 303 + lib/semmle-io/src/index.ts | 2 + lib/semmle-io/src/random-access-reader.ts | 8 + lib/semmle-io/tsconfig.json | 3 + lib/semmle-vscode-utils/gulpfile.js/index.js | 7 + lib/semmle-vscode-utils/package.json | 26 + .../src/disposable-object.ts | 63 + lib/semmle-vscode-utils/src/index.ts | 1 + lib/semmle-vscode-utils/tsconfig.json | 3 + rush.json | 58 + tools/build-tasks/gulpfile.js/index.js | 7 + tools/build-tasks/package.json | 47 + tools/build-tasks/src/deploy.ts | 189 + tools/build-tasks/src/index.ts | 4 + tools/build-tasks/src/package.ts | 23 + tools/build-tasks/src/pnpm.ts | 17 + tools/build-tasks/src/rush.ts | 146 + tools/build-tasks/src/tests.ts | 6 + tools/build-tasks/src/textmate.ts | 245 + tools/build-tasks/src/typescript.ts | 68 + tools/build-tasks/tsconfig.json | 3 + tsfmt.json | 18 + 133 files changed, 22112 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/workflows/main.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 build/README.md create mode 100644 build/package-lock.json create mode 100644 build/package.json create mode 100644 common/config/LICENSE create mode 100644 common/config/rush/.npmrc create mode 100644 common/config/rush/command-line.json create mode 100644 common/config/rush/common-versions.json create mode 100644 common/config/rush/pnpmfile.js create mode 100644 common/config/rush/shrinkwrap.yaml create mode 100644 common/config/rush/version-policies.json create mode 100644 common/scripts/install-run-rush.js create mode 100644 common/scripts/install-run.js create mode 100644 configs/typescript-config/common.tsconfig.json create mode 100644 configs/typescript-config/extension.tsconfig.json create mode 100644 configs/typescript-config/lib.tsconfig.json create mode 100644 configs/typescript-config/package.json create mode 100644 extensions/ql-vscode/.vscodeignore create mode 100644 extensions/ql-vscode/README.md create mode 100644 extensions/ql-vscode/gulpfile.js/index.js create mode 100644 extensions/ql-vscode/gulpfile.js/webpack.config.ts create mode 100644 extensions/ql-vscode/gulpfile.js/webpack.ts create mode 100644 extensions/ql-vscode/language-configuration.json create mode 100644 extensions/ql-vscode/media/VS-marketplace-CodeQL-icon.png create mode 100644 extensions/ql-vscode/media/black-plus.svg create mode 100644 extensions/ql-vscode/media/check-dark-mode.svg create mode 100644 extensions/ql-vscode/media/check-light-mode.svg create mode 100644 extensions/ql-vscode/media/logo.svg create mode 100644 extensions/ql-vscode/media/red-x.svg create mode 100644 extensions/ql-vscode/media/white-plus.svg create mode 100644 extensions/ql-vscode/package.json create mode 100644 extensions/ql-vscode/src/archive-filesystem-provider.ts create mode 100644 extensions/ql-vscode/src/blob.d.ts create mode 100644 extensions/ql-vscode/src/cli-version.ts create mode 100644 extensions/ql-vscode/src/cli.ts create mode 100644 extensions/ql-vscode/src/config.ts create mode 100644 extensions/ql-vscode/src/databases-ui.ts create mode 100644 extensions/ql-vscode/src/databases.ts create mode 100644 extensions/ql-vscode/src/distribution.ts create mode 100644 extensions/ql-vscode/src/extension.ts create mode 100644 extensions/ql-vscode/src/helpers-pure.ts create mode 100644 extensions/ql-vscode/src/helpers.ts create mode 100644 extensions/ql-vscode/src/ide-server.ts create mode 100644 extensions/ql-vscode/src/interface-types.ts create mode 100644 extensions/ql-vscode/src/interface.ts create mode 100644 extensions/ql-vscode/src/logging.ts create mode 100644 extensions/ql-vscode/src/messages.ts create mode 100644 extensions/ql-vscode/src/queries.ts create mode 100644 extensions/ql-vscode/src/query-history.ts create mode 100644 extensions/ql-vscode/src/queryserver-client.ts create mode 100644 extensions/ql-vscode/src/view/LICENSE create mode 100644 extensions/ql-vscode/src/view/alert-table.tsx create mode 100644 extensions/ql-vscode/src/view/octicons.tsx create mode 100644 extensions/ql-vscode/src/view/raw-results-table.tsx create mode 100644 extensions/ql-vscode/src/view/result-table-utils.tsx create mode 100644 extensions/ql-vscode/src/view/result-tables.tsx create mode 100644 extensions/ql-vscode/src/view/results.tsx create mode 100644 extensions/ql-vscode/src/view/resultsView.css create mode 100644 extensions/ql-vscode/src/view/tsconfig.json create mode 100644 extensions/ql-vscode/src/vscode-tests/index-template.ts create mode 100644 extensions/ql-vscode/src/vscode-tests/minimal-workspace/activation.test.ts create mode 100644 extensions/ql-vscode/src/vscode-tests/minimal-workspace/index.ts create mode 100644 extensions/ql-vscode/src/vscode-tests/no-workspace/activation.test.ts create mode 100644 extensions/ql-vscode/src/vscode-tests/no-workspace/archive-filesystem-provider.test.ts create mode 100644 extensions/ql-vscode/src/vscode-tests/no-workspace/cli-version.test.ts create mode 100644 extensions/ql-vscode/src/vscode-tests/no-workspace/data/archive-filesystem-provider-test/single_file.zip create mode 100644 extensions/ql-vscode/src/vscode-tests/no-workspace/distribution.test.ts create mode 100644 extensions/ql-vscode/src/vscode-tests/no-workspace/index.ts create mode 100644 extensions/ql-vscode/src/vscode-tests/no-workspace/webview-uri.test.ts create mode 100644 extensions/ql-vscode/src/vscode-tests/run-integration-tests.ts create mode 100644 extensions/ql-vscode/syntaxes/dbscheme.tmLanguage.yml create mode 100644 extensions/ql-vscode/syntaxes/ql.tmLanguage.yml create mode 100644 extensions/ql-vscode/test/.gitignore create mode 100644 extensions/ql-vscode/test/data/multiple-result-sets.ql create mode 100644 extensions/ql-vscode/test/data/query.ql create mode 100644 extensions/ql-vscode/test/data/test.dbscheme create mode 100644 extensions/ql-vscode/test/data/test.dbscheme.stats create mode 100644 extensions/ql-vscode/test/pure-tests/location.test.ts create mode 100644 extensions/ql-vscode/test/pure-tests/query-test.ts create mode 100644 extensions/ql-vscode/tsconfig.json create mode 100644 lib/semmle-bqrs/gulpfile.js/index.js create mode 100644 lib/semmle-bqrs/package.json create mode 100644 lib/semmle-bqrs/src/bqrs-custom.ts create mode 100644 lib/semmle-bqrs/src/bqrs-file.ts create mode 100644 lib/semmle-bqrs/src/bqrs-parse.ts create mode 100644 lib/semmle-bqrs/src/bqrs-results.ts create mode 100644 lib/semmle-bqrs/src/bqrs-schema.ts create mode 100644 lib/semmle-bqrs/src/bqrs.ts create mode 100644 lib/semmle-bqrs/src/index.ts create mode 100644 lib/semmle-bqrs/src/path-problem-query-results.ts create mode 100644 lib/semmle-bqrs/src/problem-query-results.ts create mode 100644 lib/semmle-bqrs/tsconfig.json create mode 100644 lib/semmle-io-node/gulpfile.js/index.js create mode 100644 lib/semmle-io-node/package.json create mode 100644 lib/semmle-io-node/src/file-reader.ts create mode 100644 lib/semmle-io-node/src/index.ts create mode 100644 lib/semmle-io-node/tsconfig.json create mode 100644 lib/semmle-io/gulpfile.js/index.js create mode 100644 lib/semmle-io/package.json create mode 100644 lib/semmle-io/src/digester.ts create mode 100644 lib/semmle-io/src/index.ts create mode 100644 lib/semmle-io/src/random-access-reader.ts create mode 100644 lib/semmle-io/tsconfig.json create mode 100644 lib/semmle-vscode-utils/gulpfile.js/index.js create mode 100644 lib/semmle-vscode-utils/package.json create mode 100644 lib/semmle-vscode-utils/src/disposable-object.ts create mode 100644 lib/semmle-vscode-utils/src/index.ts create mode 100644 lib/semmle-vscode-utils/tsconfig.json create mode 100644 rush.json create mode 100644 tools/build-tasks/gulpfile.js/index.js create mode 100644 tools/build-tasks/package.json create mode 100644 tools/build-tasks/src/deploy.ts create mode 100644 tools/build-tasks/src/index.ts create mode 100644 tools/build-tasks/src/package.ts create mode 100644 tools/build-tasks/src/pnpm.ts create mode 100644 tools/build-tasks/src/rush.ts create mode 100644 tools/build-tasks/src/tests.ts create mode 100644 tools/build-tasks/src/textmate.ts create mode 100644 tools/build-tasks/src/typescript.ts create mode 100644 tools/build-tasks/tsconfig.json create mode 100644 tsfmt.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..4c5adeabe --- /dev/null +++ b/.editorconfig @@ -0,0 +1,6 @@ +[*.{ts,tsx,css,js,json}] +indent_style = space +indent_size = 2 +charset = utf-8 +end_of_line = lf +trim_trailing_whitespace = true diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..59175dee0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,14 @@ +# Don't allow people to merge changes to these generated files, because the result +# may be invalid. You need to run "rush update" again. +pnpm-lock.yaml merge=binary +shrinkwrap.yaml merge=binary +npm-shrinkwrap.json merge=binary +yarn.lock merge=binary + +# Rush's JSON config files use JavaScript-style code comments. The rule below prevents pedantic +# syntax highlighters such as GitHub's from highlighting these comments as errors. Your text editor +# may also require a special configuration to allow comments in JSON. +# +# For more information, see this issue: https://github.com/Microsoft/web-build-tools/issues/1088 +# +*.json linguist-language=JSON-with-Comments diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..2890358c0 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,73 @@ +name: Build Extension +on: [push, pull_request] + +jobs: + build: + name: Build + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + steps: + - name: Checkout + uses: actions/checkout@v1 + with: + fetch-depth: 1 + + - name: Build + run: | + cd build + npm install + npm run build-ci + shell: bash + + - name: Prepare artifacts + if: matrix.os == 'ubuntu-latest' + run: | + mkdir artifacts + cp dist/*.vsix artifacts + + - name: Upload artifacts + uses: actions/upload-artifact@master + if: matrix.os == 'ubuntu-latest' + with: + name: vscode-codeql-extension + path: artifacts + + test: + name: Test + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + steps: + - name: Checkout + uses: actions/checkout@v1 + with: + fetch-depth: 1 + + # We have to build the dependencies in `lib` before running any tests. + - name: Build + run: | + cd build + npm install + npm run build-ci + shell: bash + + - name: Run unit tests + run: | + cd extensions/ql-vscode + npm run test + + - name: Run integration tests (Linux) + if: matrix.os == 'ubuntu-latest' + run: | + cd extensions/ql-vscode + sudo apt-get install xvfb + /usr/bin/xvfb-run npm run integration + + - name: Run integration tests (Windows) + if: matrix.os == 'windows-latest' + run: | + cd extensions/ql-vscode + npm run integration \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..1225304a6 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,90 @@ +# Build and release a new version of the extension. +# Based on example workflow at https://github.com/actions/upload-release-asset +# licensed under https://github.com/actions/upload-release-asset/blob/master/LICENSE. +# Reference for passing data between steps: +# https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps + +name: Release +on: + push: + # Path filters are not evaluated for pushes to tags. + # (source: https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#onpushpull_requestpaths) + # So this workflow is triggered in the following events: + # - Release event: a SemVer tag, e.g. v1.0.0 or v1.0.0-alpha, is pushed + tags: + - 'v[0-9]+.[0-9]+.[0-9]+*' + # OR + # - Test event: this file is modified on a branch in the main repo containing `/actions/` in the name. + branches: + - '**/actions/**' + paths: + - '**/workflows/release.yml' + +jobs: + build: + name: Release + runs-on: ubuntu-latest + # TODO Share steps with the main workflow. + steps: + - name: Checkout + uses: actions/checkout@master + + - name: Build + run: | + cd build + npm install + # Release build instead of dev build. + npm run build-release + shell: bash + + - name: Prepare artifacts + id: prepare-artifacts + run: | + mkdir artifacts + cp dist/*.vsix artifacts + # Record the VSIX path as an output of this step. + # This will be used later when uploading a release asset. + VSIX_PATH="$(ls dist/*.vsix)" + echo "::set-output name=vsix_path::$VSIX_PATH" + # Transform the GitHub ref so it can be used in a filename. + # This is mainly needed for testing branches that modify this workflow. + REF_NAME="$(echo ${{ github.ref }} | sed -e 's:/:-:g')" + echo "::set-output name=ref_name::$REF_NAME" + + # Uploading artifacts is not necessary to create a release. + # This is just in case the release itself fails and we want to access the built artifacts from Actions. + # TODO Remove if not useful. + - name: Upload artifacts + uses: actions/upload-artifact@master + with: + name: vscode-codeql-extension + path: artifacts + + # TODO Run tests, or check that a test run on the same branch succeeded. + + - name: Create release + id: create-release + uses: actions/create-release@v1.0.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + # This gives us a chance to manually review the created release before publishing it, + # as well as to test the release workflow by pushing temporary tags. + # Once we have set all required release metadata in this step, we can set this to `false`. + draft: true + prerelease: false + + - name: Upload release asset + uses: actions/upload-release-asset@v1.0.1 + if: success() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + # Get the `upload_url` from the `create-release` step above. + upload_url: ${{ steps.create-release.outputs.upload_url }} + # Get the `vsix_path` and `ref_name` from the `prepare-artifacts` step above. + asset_path: ${{ steps.prepare-artifacts.outputs.vsix_path }} + asset_name: ${{ format('vscode-codeql-{0}.vsix', steps.prepare-artifacts.outputs.ref_name) }} + asset_content_type: application/zip \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..67c7df440 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# Logs +*.log + +# Generated files +/dist/ +out/ +server/ +node_modules/ +gen/ + +# Integration test artifacts +**/.vscode-test/** + +# Visual Studio workspace state +.vs/ + +# Rush files +/common/temp/** +package-deps.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..23132f86c --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "eamodio.tsl-problem-matcher" + ], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [] +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..d466ad522 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,84 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Extension (vscode-codeql)", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceRoot}/dist/vscode-codeql", + "--disable-extensions" + ], + "stopOnEntry": false, + "sourceMaps": true, + "outFiles": [ + "${workspaceRoot}/dist/vscode-codeql/out/**/*.js", + "${workspaceRoot}/dist/vscode-codeql/node_modules/semmle-bqrs/out/**/*.js", + "${workspaceRoot}/dist/vscode-codeql/node_modules/semmle-io/out/**/*.js", + "${workspaceRoot}/dist/vscode-codeql/node_modules/semmle-io-node/out/**/*.js", + "${workspaceRoot}/dist/vscode-codeql/node_modules/semmle-vscode-utils/out/**/*.js" + ], + "preLaunchTask": "Build" + }, + { + "name": "Launch Unit Tests (vscode-codeql)", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceRoot}/dist/vscode-codeql", + "--extensionTestsPath=${workspaceRoot}/extensions/ql-vscode/out/test", + "--disable-extensions" + ], + "stopOnEntry": false, + "sourceMaps": true, + "outFiles": [ + "${workspaceRoot}/dist/vscode-codeql/out/**/*.js", + "${workspaceRoot}/dist/vscode-codeql/node_modules/semmle-bqrs/out/**/*.js", + "${workspaceRoot}/dist/vscode-codeql/node_modules/semmle-io/out/**/*.js", + "${workspaceRoot}/dist/vscode-codeql/node_modules/semmle-io-node/out/**/*.js", + "${workspaceRoot}/dist/vscode-codeql/node_modules/semmle-vscode-utils/out/**/*.js", + "${workspaceRoot}/extensions/ql-vscode/out/test/**/*.js" + ], + "preLaunchTask": "Build" + }, + { + "name": "Launch Integration Tests - No Workspace (vscode-codeql)", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceRoot}/dist/vscode-codeql", + "--extensionTestsPath=${workspaceRoot}/extensions/ql-vscode/out/vscode-tests/no-workspace/index", + "--disable-extensions" + ], + "stopOnEntry": false, + "sourceMaps": true, + "outFiles": [ + "${workspaceRoot}/dist/vscode-codeql/out/**/*.js", + "${workspaceRoot}/extensions/ql-vscode/out/vscode-tests/**/*.js" + ], + "preLaunchTask": "Build" + }, + { + "name": "Launch Integration Tests - Minimal Workspace (vscode-codeql)", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceRoot}/dist/vscode-codeql", + "--extensionTestsPath=${workspaceRoot}/extensions/ql-vscode/out/vscode-tests/minimal-workspace/index", + "${workspaceRoot}/extensions/ql-vscode/test/data", + ], + "stopOnEntry": false, + "sourceMaps": true, + "outFiles": [ + "${workspaceRoot}/dist/vscode-codeql/out/**/*.js", + "${workspaceRoot}/extensions/ql-vscode/out/vscode-tests/**/*.js" + ], + "preLaunchTask": "Build" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..11570a9b5 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,14 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "files.exclude": { + "out": false // set this to true to hide the "out" folder with the compiled JS files + }, + "files.watcherExclude": { + "**/.git/**": true, + "**/node_modules/*/**": true + }, + "search.exclude": { + "out": true // set this to false to include "out" folder in search results + }, + "typescript.tsdk": "./common/temp/node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..871c6c5dc --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,105 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Build", + "type": "shell", + "group": { + "kind": "build", + "isDefault": true + }, + "command": "node common/scripts/install-run-rush.js build --verbose", + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": true + }, + "problemMatcher": [ + { + "owner": "typescript", + "fileLocation": "absolute", + "pattern": { + "regexp": "^\\[gulp-typescript\\] ([^(]+)\\((\\d+|\\d+,\\d+|\\d+,\\d+,\\d+,\\d+)\\): error TS\\d+: (.*)$", + "file": 1, + "location": 2, + "message": 3 + }, + }, + "$ts-webpack" + ] + }, + { + "label": "Rebuild", + "type": "shell", + "group": "build", + "command": "node common/scripts/install-run-rush.js rebuild --verbose", + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": true + }, + "problemMatcher": [ + { + "owner": "typescript", + "fileLocation": "absolute", + "pattern": { + "regexp": "^\\[gulp-typescript\\] ([^(]+)\\((\\d+|\\d+,\\d+|\\d+,\\d+,\\d+,\\d+)\\): error TS\\d+: (.*)$", + "file": 1, + "location": 2, + "message": 3 + } + } + ] + }, + { + "label": "Update", + "type": "shell", + "command": "node common/scripts/install-run-rush.js update", + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": true + }, + "problemMatcher": [] + }, + { + "label": "Update (full)", + "type": "shell", + "command": "node common/scripts/install-run-rush.js update --full", + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": true + }, + "problemMatcher": [] + }, + { + "label": "Format", + "type": "shell", + "command": "node common/scripts/install-run-rush.js format", + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": true + }, + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..3a64696bc --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to make participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies within all project spaces, and it also applies when +an individual is representing the project or its community in public spaces. +Examples of representing a project or community include using an official +project e-mail address, posting via an official social media account, or acting +as an appointed representative at an online or offline event. Representation of +a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at opensource@github.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..96502df3e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,135 @@ +## Contributing + +[fork]: https://github.com/github/vscode-codeql/fork +[pr]: https://github.com/github/vscode-codeql/compare +[style]: https://primer.style +[code-of-conduct]: CODE_OF_CONDUCT.md + +Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. + +Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE.md). + +Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms. + +## Submitting a pull request + +0. [Fork][fork] and clone the repository +0. Set up a local build +0. Create a new branch: `git checkout -b my-branch-name` +0. Make your change +0. Push to your fork and [submit a pull request][pr] +0. Pat yourself on the back and wait for your pull request to be reviewed and merged. + +Here are a few things you can do that will increase the likelihood of your pull request being accepted: + +- Follow the [style guide][style]. +- Write tests. Tests that don't require the VS Code API are located [here](extensions/ql-vscode/test). Integration tests that do require the VS Code API are located [here](extensions/ql-vscode/src/vscode-tests). +- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. +- Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). + +## Setting up a local build + +Make sure you have a fairly recent version of vscode (>1.32) and are using nodejs +version >=v10.13.0. (Tested on v10.15.1 and v10.16.0). + +This repo uses [Rush](https://rushjs.io) to handle package management, building, and other +operations across multiple projects. See the Rush "[Getting started as a developer](https://rushjs.io/pages/developer/new_developer/)" docs +for more details. + +If you plan on building from the command line, it's easiest if Rush is installed globally: + +```shell +npm install -g @microsoft/rush +``` + +Note that when you run the `rush` command from the globally installed version, it will examine the +`rushVersion` property in the repo's `rush.json`, and if it differs from the globally installed +version, it will download, cache, and run the version of Rush specified in the `rushVersion` +property. + +If you plan on only building via VS Code tasks, you don't need Rush installed at all, since those +tasks run `common/scripts/install-run-rush.js` to bootstrap a locally installed and cached copy of +Rush. + +### Building + +#### Installing all packages (instead of `npm install`) + +After updating any `package.json` file, or after checking or pulling a new branch, you need to +make sure all the right npm packages are installed, which you would normally do via `npm install` in +a single-project repo. With Rush, you need to do an "update" instead: + +##### From VS Code + +`Terminal > Run Task... > Update` + +##### From the command line + +```shell +$ rush update +``` + +#### Building all projects (instead of `gulp`) + +Rush builds all projects in the repo, in dependency order, building multiple projects in parallel +where possible. By default, the build also packages the extension itself into a .vsix file in the +`dist` directory. To build: + +##### From VS Code + +`Terminal > Run Build Task...` (or just `Ctrl+Shift+B` with the default key bindings) + +##### From the command line + +```shell +rush build --verbose +``` + +#### Forcing a clean build + +Rush does a reasonable job of detecting on its own which projects need to be rebuilt, but if you need to +force a full rebuild of all projects: + +##### From VS Code + +`Terminal > Run Task... > Rebuild` + +##### From the command line + +```shell +rush rebuild --verbose +``` + +### Installing + +You can install the `.vsix` file from within VS Code itself, from the Extensions container in the sidebar: + +`More Actions...` (top right) `> Install from VSIX...` + +Or, from the command line, use something like (depending on where you have VSCode installed): + +```shell +$ code --install-extension dist/vscode-codeql-*.vsix # normal VSCode installation +# or maybe +$ vscode/scripts/code-cli.sh --install-extension dist/vscode-codeql-*.vsix # if you're using the open-source version from a checkout of https://github.com/microsoft/vscode +``` + +### Debugging + +You can use VS Code to debug the extension without explicitly installing it. Just open this directory as a workspace in VS Code, and hit `F5` to start a debugging session. + +## Releasing (write access required) + +1. Trigger a release build on Actions by adding a new tag on master of the format `vxx.xx.xx` +1. Monitor the status of the release build in the `Release` workflow in the Actions tab. +1. Download the VSIX from the draft GitHub release that is created when the release build finishes. +1. Log into the [Visual Studio Marketplace](https://marketplace.visualstudio.com/manage/publishers/github). +1. Click the `...` menu in the CodeQL row and click **Update**. +1. Drag the `.vsix` file you downloaded from the GitHub release into the Marketplace and click **Upload**. +1. Publish the GitHub release. + +## Resources + +- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) +- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) +- [GitHub Help](https://help.github.com) diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 000000000..df3369d44 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,20 @@ +Copyright (c) 2019 GitHub, Inc. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000..df7448474 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# CodeQL for Visual Studio Code + +This project is an extension for Visual Studio Code that adds rich language support for CodeQL. It's used to find problems in code bases using CodeQL. It's written primarily in TypeScript. + +The extension is released. You can download it from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=github.vscode-codeql). + +![CI status badge](https://github.com/github/vscode-codeql/workflows/Build%20Extension/badge.svg) + +## Features + +* Enables you to use CodeQL to query databases and discover problems in codebases. +* Shows the flow of data through the results of path queries, which is essential for triaging security results. +* Provides an easy way to run queries from the large, open source repository of [CodeQL security queries](https://github.com/Semmle/ql). +* Adds IntelliSense to support you writing and editing your own CodeQL query and library files. + + +## Project goals and scope + +This project will track new feature development in CodeQL and, whenever appropriate, bring that functionality to the Visual Studio Code experience. + +## Contributing + +This project welcomes contributions. See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to build, install, and contribute. + +## License + +The CodeQL extension for Visual Studio Code is [licensed](LICENSE.md) under the MIT License. The version of CodeQL used by the CodeQL extension is subject to the [CodeQL Research Terms & Conditions](https://securitylab.github.com/tools/codeql/license). + +When using the GitHub logos, be sure to follow the [GitHub logo guidelines](https://github.com/logos). diff --git a/build/README.md b/build/README.md new file mode 100644 index 000000000..7daee6a0f --- /dev/null +++ b/build/README.md @@ -0,0 +1,12 @@ +GitHub Actions Build directory +=== + +The point of this directory is to allow us to do a local installation *of* the rush +tool, since + - installing globally is not permitted on github actions + - installing locally in the root directory of the repo creates `node_modules` there, + and rush itself gives error messages since it thinks `node_modules` is not supposed + to exist, since rush is supposed to be managing subproject dependencies. + +Running rush from a subdirectory searches parent directories for `rush.json` +and does the build starting from that file's location. diff --git a/build/package-lock.json b/build/package-lock.json new file mode 100644 index 000000000..721d18b52 --- /dev/null +++ b/build/package-lock.json @@ -0,0 +1,1294 @@ +{ + "name": "build", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@microsoft/node-core-library": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@microsoft/node-core-library/-/node-core-library-3.13.0.tgz", + "integrity": "sha512-mnsL/1ikVWHl8sPNssavaAgtUaIM3hkQ8zeySuApU5dNmsMPzovJPfx9m5JGiMvs1v5QNAIVeiS9jnWwe/7anw==", + "requires": { + "@types/fs-extra": "5.0.4", + "@types/jju": "~1.4.0", + "@types/node": "8.5.8", + "@types/z-schema": "3.16.31", + "colors": "~1.2.1", + "fs-extra": "~7.0.1", + "jju": "~1.4.0", + "z-schema": "~3.18.3" + } + }, + "@microsoft/package-deps-hash": { + "version": "2.2.163", + "resolved": "https://registry.npmjs.org/@microsoft/package-deps-hash/-/package-deps-hash-2.2.163.tgz", + "integrity": "sha512-SNxsz1i1oUFqlQSSsibtbLRlmftlx6EHMObqv9XjxJpE+oHJSi8r6tobJY8k7l2r0lfkZwR5BHJTwjzVpOWfXA==" + }, + "@microsoft/rush": { + "version": "5.10.3", + "resolved": "https://registry.npmjs.org/@microsoft/rush/-/rush-5.10.3.tgz", + "integrity": "sha512-U2DEHGuVFtCm73LZvZHziS5OVOImd0qP9o5XHE5tTwX0u7UIC6w8GMCOi7rV/pFiFlOOuSAMfbt7rVscSmiA4Q==", + "requires": { + "@microsoft/node-core-library": "3.13.0", + "@microsoft/rush-lib": "5.10.3", + "colors": "~1.2.1", + "semver": "~5.3.0" + } + }, + "@microsoft/rush-lib": { + "version": "5.10.3", + "resolved": "https://registry.npmjs.org/@microsoft/rush-lib/-/rush-lib-5.10.3.tgz", + "integrity": "sha512-OjZbiI6zleoK8YPSGChZxa2upS1jRbYbm+MVdGTf95F80MirVydb+dZADnun9OmwYR0IxjB5K7VVNN3ByYtuRg==", + "requires": { + "@microsoft/node-core-library": "3.13.0", + "@microsoft/package-deps-hash": "2.2.163", + "@microsoft/stream-collator": "3.0.78", + "@microsoft/ts-command-line": "4.2.6", + "@pnpm/link-bins": "~1.0.1", + "@pnpm/logger": "~1.0.1", + "@types/inquirer": "0.0.43", + "@yarnpkg/lockfile": "~1.0.2", + "builtins": "~1.0.3", + "cli-table": "~0.3.1", + "colors": "~1.2.1", + "git-repo-info": "~2.1.0", + "glob": "~7.0.5", + "glob-escape": "~0.0.1", + "https-proxy-agent": "~2.2.1", + "inquirer": "~6.2.0", + "js-yaml": "~3.13.1", + "lodash": "~4.17.5", + "minimatch": "~3.0.2", + "node-fetch": "~2.1.2", + "npm-package-arg": "~5.1.2", + "read-package-tree": "~5.1.5", + "semver": "~5.3.0", + "strict-uri-encode": "~2.0.0", + "tar": "~4.4.1", + "wordwrap": "~1.0.0", + "z-schema": "~3.18.3" + } + }, + "@microsoft/stream-collator": { + "version": "3.0.78", + "resolved": "https://registry.npmjs.org/@microsoft/stream-collator/-/stream-collator-3.0.78.tgz", + "integrity": "sha512-VGL+ue8q/RQLtfgNotmvlhJHYFkXLZslAT7QWkhan8zDwSy4AFtjCIikxfpyFDnQnNT61rEJ8PgvowjwPCTVxw==", + "requires": { + "@types/node": "8.5.8", + "colors": "~1.2.1" + } + }, + "@microsoft/ts-command-line": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@microsoft/ts-command-line/-/ts-command-line-4.2.6.tgz", + "integrity": "sha512-GFLPg9Z5yiNca3di/V6Zt3tAvj1de9EK0eL88tE+1eckQSH405UQcm7D+H8LbEhRpqpG+ZqN9LXCAEw4L5uchg==", + "requires": { + "@types/argparse": "1.0.33", + "@types/node": "8.5.8", + "argparse": "~1.0.9", + "colors": "~1.2.1" + } + }, + "@pnpm/link-bins": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@pnpm/link-bins/-/link-bins-1.0.3.tgz", + "integrity": "sha512-thVgwrQ5rMcPYI6a0IPOt2pnlF1n5zX7BN4CrFeBp0/JCGsZAht/VOPv9bD3cZ+j0vDemEwE23BfhOWxmxq2yQ==", + "requires": { + "@pnpm/package-bins": "^1.0.0", + "@pnpm/types": "^1.7.0", + "@types/mz": "^0.0.32", + "@types/node": "^9.6.5 || 10", + "@types/ramda": "^0.25.20", + "@zkochan/cmd-shim": "^2.2.4", + "arr-flatten": "^1.1.0", + "is-windows": "^1.0.2", + "mkdirp-promise": "^5.0.1", + "mz": "^2.7.0", + "normalize-path": "^3.0.0", + "p-filter": "^1.0.0", + "ramda": "^0.25.0", + "read-package-json": "^2.0.13" + }, + "dependencies": { + "@types/node": { + "version": "10.14.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.19.tgz", + "integrity": "sha512-j6Sqt38ssdMKutXBUuAcmWF8QtHW1Fwz/mz4Y+Wd9mzpBiVFirjpNQf363hG5itkG+yGaD+oiLyb50HxJ36l9Q==" + } + } + }, + "@pnpm/logger": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/logger/-/logger-1.0.2.tgz", + "integrity": "sha512-A8XbJKvdueazvJGPn1qQ9LL6uopV88ebIT+dJKNQ68gT7yfCbtfT8j5ZzdVczmGbkiuBeZ1VckZerkO0tjOXZA==", + "requires": { + "@types/node": "^9.4.0 || 10", + "bole": "^3.0.2", + "ndjson": "^1.5.0" + }, + "dependencies": { + "@types/node": { + "version": "10.14.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.19.tgz", + "integrity": "sha512-j6Sqt38ssdMKutXBUuAcmWF8QtHW1Fwz/mz4Y+Wd9mzpBiVFirjpNQf363hG5itkG+yGaD+oiLyb50HxJ36l9Q==" + } + } + }, + "@pnpm/package-bins": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pnpm/package-bins/-/package-bins-1.0.0.tgz", + "integrity": "sha512-ZqVfIXK3r5AsP5VAhPHrhf3isF+T4yEuUpJTF9T03oFTJ9LBnkKvx8F7P7biKEManxSGOkSpNoIBdsura9pY5Q==", + "requires": { + "@pnpm/types": "^1.7.0", + "@types/mz": "^0.0.32", + "mz": "^2.7.0", + "p-filter": "^1.0.0" + } + }, + "@pnpm/types": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@pnpm/types/-/types-1.8.0.tgz", + "integrity": "sha512-NsEzBVa5aMgn/n79piyJtpUQFzJ97tB2R2r8PSJlLnMA6LJmchKuv7ATN+/nZH/3QRd/+uFXEq07/i/ajsqVGQ==" + }, + "@types/argparse": { + "version": "1.0.33", + "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.33.tgz", + "integrity": "sha512-VQgHxyPMTj3hIlq9SY1mctqx+Jj8kpQfoLvDlVSDNOyuYs8JYfkuY3OW/4+dO657yPmNhHpePRx0/Tje5ImNVQ==" + }, + "@types/fs-extra": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.0.4.tgz", + "integrity": "sha512-DsknoBvD8s+RFfSGjmERJ7ZOP1HI0UZRA3FSI+Zakhrc/Gy26YQsLI+m5V5DHxroHRJqCDLKJp7Hixn8zyaF7g==", + "requires": { + "@types/node": "*" + } + }, + "@types/inquirer": { + "version": "0.0.43", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-0.0.43.tgz", + "integrity": "sha512-xgyfKZVMFqE8aIKy1xfFVsX2MxyXUNgjgmbF6dRbR3sL+ZM5K4ka/9L4mmTwX8eTeVYtduyXu0gUVwVJa1HbNw==", + "requires": { + "@types/rx": "*", + "@types/through": "*" + } + }, + "@types/jju": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@types/jju/-/jju-1.4.1.tgz", + "integrity": "sha512-LFt+YA7Lv2IZROMwokZKiPNORAV5N3huMs3IKnzlE430HWhWYZ8b+78HiwJXJJP1V2IEjinyJURuRJfGoaFSIA==" + }, + "@types/mz": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/mz/-/mz-0.0.32.tgz", + "integrity": "sha512-cy3yebKhrHuOcrJGkfwNHhpTXQLgmXSv1BX+4p32j+VUQ6aP2eJ5cL7OvGcAQx75fCTFaAIIAKewvqL+iwSd4g==", + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.5.8.tgz", + "integrity": "sha512-8KmlRxwbKZfjUHFIt3q8TF5S2B+/E5BaAoo/3mgc5h6FJzqxXkCK/VMetO+IRDtwtU6HUvovHMBn+XRj7SV9Qg==" + }, + "@types/ramda": { + "version": "0.25.51", + "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.25.51.tgz", + "integrity": "sha512-xcmtfHIgF9SYjhGdsZR1nQslxG4hu0cIpFfLQ4CWdw3KzHvl7ki1AzFLQUkbDTG42ZN3ZsQfdRzXRlkAvbIy5Q==" + }, + "@types/rx": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@types/rx/-/rx-4.1.1.tgz", + "integrity": "sha1-WY/JSla67ZdfGUV04PVy/Y5iekg=", + "requires": { + "@types/rx-core": "*", + "@types/rx-core-binding": "*", + "@types/rx-lite": "*", + "@types/rx-lite-aggregates": "*", + "@types/rx-lite-async": "*", + "@types/rx-lite-backpressure": "*", + "@types/rx-lite-coincidence": "*", + "@types/rx-lite-experimental": "*", + "@types/rx-lite-joinpatterns": "*", + "@types/rx-lite-testing": "*", + "@types/rx-lite-time": "*", + "@types/rx-lite-virtualtime": "*" + } + }, + "@types/rx-core": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/rx-core/-/rx-core-4.0.3.tgz", + "integrity": "sha1-CzNUsSOM7b4rdPYybxOdvHpZHWA=" + }, + "@types/rx-core-binding": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/rx-core-binding/-/rx-core-binding-4.0.4.tgz", + "integrity": "sha512-5pkfxnC4w810LqBPUwP5bg7SFR/USwhMSaAeZQQbEHeBp57pjKXRlXmqpMrLJB4y1oglR/c2502853uN0I+DAQ==", + "requires": { + "@types/rx-core": "*" + } + }, + "@types/rx-lite": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/rx-lite/-/rx-lite-4.0.6.tgz", + "integrity": "sha512-oYiDrFIcor9zDm0VDUca1UbROiMYBxMLMaM6qzz4ADAfOmA9r1dYEcAFH+2fsPI5BCCjPvV9pWC3X3flbrvs7w==", + "requires": { + "@types/rx-core": "*", + "@types/rx-core-binding": "*" + } + }, + "@types/rx-lite-aggregates": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/rx-lite-aggregates/-/rx-lite-aggregates-4.0.3.tgz", + "integrity": "sha512-MAGDAHy8cRatm94FDduhJF+iNS5//jrZ/PIfm+QYw9OCeDgbymFHChM8YVIvN2zArwsRftKgE33QfRWvQk4DPg==", + "requires": { + "@types/rx-lite": "*" + } + }, + "@types/rx-lite-async": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/rx-lite-async/-/rx-lite-async-4.0.2.tgz", + "integrity": "sha512-vTEv5o8l6702ZwfAM5aOeVDfUwBSDOs+ARoGmWAKQ6LOInQ8J4/zjM7ov12fuTpktUKdMQjkeCp07Vd73mPkxw==", + "requires": { + "@types/rx-lite": "*" + } + }, + "@types/rx-lite-backpressure": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/rx-lite-backpressure/-/rx-lite-backpressure-4.0.3.tgz", + "integrity": "sha512-Y6aIeQCtNban5XSAF4B8dffhIKu6aAy/TXFlScHzSxh6ivfQBQw6UjxyEJxIOt3IT49YkS+siuayM2H/Q0cmgA==", + "requires": { + "@types/rx-lite": "*" + } + }, + "@types/rx-lite-coincidence": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/rx-lite-coincidence/-/rx-lite-coincidence-4.0.3.tgz", + "integrity": "sha512-1VNJqzE9gALUyMGypDXZZXzR0Tt7LC9DdAZQ3Ou/Q0MubNU35agVUNXKGHKpNTba+fr8GdIdkC26bRDqtCQBeQ==", + "requires": { + "@types/rx-lite": "*" + } + }, + "@types/rx-lite-experimental": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/rx-lite-experimental/-/rx-lite-experimental-4.0.1.tgz", + "integrity": "sha1-xTL1y98/LBXaFt7Ykw0bKYQCPL0=", + "requires": { + "@types/rx-lite": "*" + } + }, + "@types/rx-lite-joinpatterns": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/rx-lite-joinpatterns/-/rx-lite-joinpatterns-4.0.1.tgz", + "integrity": "sha1-9w/jcFGKhDLykVjMkv+1a05K/D4=", + "requires": { + "@types/rx-lite": "*" + } + }, + "@types/rx-lite-testing": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/rx-lite-testing/-/rx-lite-testing-4.0.1.tgz", + "integrity": "sha1-IbGdEfTf1v/vWp0WSOnIh5v+Iek=", + "requires": { + "@types/rx-lite-virtualtime": "*" + } + }, + "@types/rx-lite-time": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/rx-lite-time/-/rx-lite-time-4.0.3.tgz", + "integrity": "sha512-ukO5sPKDRwCGWRZRqPlaAU0SKVxmWwSjiOrLhoQDoWxZWg6vyB9XLEZViKOzIO6LnTIQBlk4UylYV0rnhJLxQw==", + "requires": { + "@types/rx-lite": "*" + } + }, + "@types/rx-lite-virtualtime": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/rx-lite-virtualtime/-/rx-lite-virtualtime-4.0.3.tgz", + "integrity": "sha512-3uC6sGmjpOKatZSVHI2xB1+dedgml669ZRvqxy+WqmGJDVusOdyxcKfyzjW0P3/GrCiN4nmRkLVMhPwHCc5QLg==", + "requires": { + "@types/rx-lite": "*" + } + }, + "@types/through": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.29.tgz", + "integrity": "sha512-9a7C5VHh+1BKblaYiq+7Tfc+EOmjMdZaD1MYtkQjSoxgB69tBjW98ry6SKsi4zEIWztLOMRuL87A3bdT/Fc/4w==", + "requires": { + "@types/node": "*" + } + }, + "@types/z-schema": { + "version": "3.16.31", + "resolved": "https://registry.npmjs.org/@types/z-schema/-/z-schema-3.16.31.tgz", + "integrity": "sha1-LrHQCl5Ow/pYx2r94S4YK2bcXBw=" + }, + "@yarnpkg/lockfile": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.0.2.tgz", + "integrity": "sha512-MqJ00WXw89ga0rK6GZkdmmgv3bAsxpJixyTthjcix73O44pBqotyU2BejBkLuIsaOBI6SEu77vAnSyLe5iIHkw==" + }, + "@zkochan/cmd-shim": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@zkochan/cmd-shim/-/cmd-shim-2.2.4.tgz", + "integrity": "sha512-BDy1oz6aFYyY73618IkXzJzFghnXwVZDc3SVa6MVKTrrk4RgubahAF5yKK+Mx4a78tfO0OHeZnJKPs0pNy5uNA==", + "requires": { + "is-windows": "^1.0.0", + "mkdirp-promise": "^5.0.1", + "mz": "^2.5.0" + } + }, + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "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==" + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "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.0" + } + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "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==" + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "bole": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bole/-/bole-3.0.2.tgz", + "integrity": "sha1-vIpIPKlASdqbg3wa0Rzf6+5uBRQ=", + "requires": { + "fast-safe-stringify": "~1.1.0", + "individual": "~3.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "builtins": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", + "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=" + }, + "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.3.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, + "chownr": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", + "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==" + }, + "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=", + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-table": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", + "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", + "requires": { + "colors": "1.0.3" + }, + "dependencies": { + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" + } + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" + }, + "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=" + }, + "colors": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.2.5.tgz", + "integrity": "sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg==" + }, + "commander": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.1.tgz", + "integrity": "sha512-cCuLsMhJeWQ/ZpsFTbE765kvVfoeSddc4nU3up4fV+fDBcfUXnbITJ+JzhkdjzOqhURjZgujxaioam4RM9yGUg==", + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "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=" + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "debuglog": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", + "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=" + }, + "dezalgo": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", + "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "fast-safe-stringify": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-1.1.13.tgz", + "integrity": "sha1-oB6c2cnkkXFcmKdaQtXwu9EH/3Y=" + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "requires": { + "minipass": "^2.6.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "git-repo-info": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/git-repo-info/-/git-repo-info-2.1.0.tgz", + "integrity": "sha512-+kigfDB7j3W80f74BoOUX+lKOmf4pR3/i2Ww6baKTCPe2hD4FRdjhV3s4P5Dy0Tak1uY1891QhKoYNtnyX2VvA==" + }, + "glob": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-escape": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/glob-escape/-/glob-escape-0.0.2.tgz", + "integrity": "sha1-nCf3gh7RwTd1gvPv2VWOP2dWKO0=" + }, + "graceful-fs": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", + "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "hosted-git-info": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz", + "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==" + }, + "https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "individual": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/individual/-/individual-3.0.0.tgz", + "integrity": "sha1-58pPhfiVewGHNPKFdQ3CLsL5hi0=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "inquirer": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.2.tgz", + "integrity": "sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA==", + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.11", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.0.0", + "through": "^2.3.6" + } + }, + "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=" + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, + "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==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha1-o6vicYryQaKykE+EpiWXDzia4yo=" + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "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==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, + "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==" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "minipass": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.8.6.tgz", + "integrity": "sha512-lFG7d6g3+/UaFDCOtqPiKAC9zngWWsQZl1g5q6gaONqrjq61SX2xFqXMleQiFVyDpYwa018E9hmlAFY22PCb+A==", + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.2.tgz", + "integrity": "sha512-lsNFqSHdJ21EwKzCp12HHJGxSMtHkCW1EMA9cceG3MkMNARjuWotZnMe3NKNshAvFXpm4loZqmYsCmRwhS2JMw==", + "requires": { + "minipass": "^2.9.0" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "mkdirp-promise": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz", + "integrity": "sha1-6bj2jlUsaKnBcTuEiD96HdA5uKE=", + "requires": { + "mkdirp": "*" + } + }, + "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=" + }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "requires": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "ndjson": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/ndjson/-/ndjson-1.5.0.tgz", + "integrity": "sha1-rmA7NrE0vOw0e0UkIrC/mNWDLsg=", + "requires": { + "json-stringify-safe": "^5.0.1", + "minimist": "^1.2.0", + "split2": "^2.1.0", + "through2": "^2.0.3" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "node-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz", + "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=" + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "npm-package-arg": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-5.1.2.tgz", + "integrity": "sha512-wJBsrf0qpypPT7A0LART18hCdyhpCMxeTtcb0X4IZO2jsP6Om7EHN1d9KSKiqD+KVH030RVNpWS9thk+pb7wzA==", + "requires": { + "hosted-git-info": "^2.4.2", + "osenv": "^0.1.4", + "semver": "^5.1.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-filter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-1.0.0.tgz", + "integrity": "sha1-Yp0xcVAgnI/VCLoTdxPvS7kg6ds=", + "requires": { + "p-map": "^1.0.0" + } + }, + "p-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", + "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "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==" + }, + "ramda": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz", + "integrity": "sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==" + }, + "read-package-json": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.0.tgz", + "integrity": "sha512-KLhu8M1ZZNkMcrq1+0UJbR8Dii8KZUqB0Sha4mOx/bknfKI/fyrQVrG/YIt2UOtG667sD8+ee4EXMM91W9dC+A==", + "requires": { + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "json-parse-better-errors": "^1.0.1", + "normalize-package-data": "^2.0.0", + "slash": "^1.0.0" + }, + "dependencies": { + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "read-package-tree": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.1.6.tgz", + "integrity": "sha512-FCX1aT3GWyY658wzDICef4p+n0dB+ENRct8E/Qyvppj6xVpOYerBHfUu7OP5Rt1/393Tdglguf5ju5DEX4wZNg==", + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "once": "^1.3.0", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdir-scoped-modules": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", + "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "requires": { + "path-parse": "^1.0.6" + } + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "requires": { + "is-promise": "^2.1.0" + } + }, + "rxjs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", + "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "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==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==" + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" + }, + "split2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", + "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", + "requires": { + "through2": "^2.0.2" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.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==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + } + } + }, + "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" + } + }, + "tar": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + } + }, + "thenify": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", + "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", + "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", + "requires": { + "builtins": "^1.0.3" + } + }, + "validator": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-8.2.0.tgz", + "integrity": "sha512-Yw5wW34fSv5spzTXNkokD6S6/Oq92d8q/t14TqsS3fAiA1RYnxSFSIZ+CY3n6PGGRCq5HhJTSepQvFUS2QUDxA==" + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "yallist": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.0.tgz", + "integrity": "sha512-6gpP93MR+VOOehKbCPchro3wFZNSNmek8A2kbkOAZLIZAYx1KP/zAqwO0sOHi3xJEb+UBz8NaYt/17UNit1Q9w==" + }, + "z-schema": { + "version": "3.18.4", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-3.18.4.tgz", + "integrity": "sha512-DUOKC/IhbkdLKKiV89gw9DUauTV8U/8yJl1sjf6MtDmzevLKOF2duNJ495S3MFVjqZarr+qNGCPbkg4mu4PpLw==", + "requires": { + "commander": "^2.7.1", + "lodash.get": "^4.0.0", + "lodash.isequal": "^4.0.0", + "validator": "^8.0.0" + } + } + } +} diff --git a/build/package.json b/build/package.json new file mode 100644 index 000000000..22ba01872 --- /dev/null +++ b/build/package.json @@ -0,0 +1,17 @@ +{ + "name": "build", + "version": "1.0.0", + "description": "", + "main": "index.js", + "dependencies": { + "@microsoft/rush": "^5.10.3" + }, + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "rush update && rush build", + "build-ci": "rush install && rush build", + "build-release": "rush install && rush build --release" + }, + "author": "GitHub" +} \ No newline at end of file diff --git a/common/config/LICENSE b/common/config/LICENSE new file mode 100644 index 000000000..6908c7e00 --- /dev/null +++ b/common/config/LICENSE @@ -0,0 +1,28 @@ +This directory contains content from https://github.com/microsoft/rushstack, +used under the MIT license as follows. +See https://github.com/microsoft/rushstack/blob/master/stack/rush-stack/LICENSE. + +@microsoft/rush-stack + +Copyright (c) Microsoft Corporation. All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/common/config/rush/.npmrc b/common/config/rush/.npmrc new file mode 100644 index 000000000..237d30461 --- /dev/null +++ b/common/config/rush/.npmrc @@ -0,0 +1,12 @@ +# Rush uses this file to configure the package registry, regardless of whether the +# package manager is PNPM, NPM, or Yarn. Prior to invoking the package manager, +# Rush will always copy this file to the folder where installation is performed. +# When NPM is the package manager, Rush works around NPM's processing of +# undefined environment variables by deleting any lines that reference undefined +# environment variables. +# +# DO NOT SPECIFY AUTHENTICATION CREDENTIALS IN THIS FILE. It should only be used +# to configure registry sources. + +registry=https://registry.npmjs.org/ +always-auth=false diff --git a/common/config/rush/command-line.json b/common/config/rush/command-line.json new file mode 100644 index 000000000..8078b67f9 --- /dev/null +++ b/common/config/rush/command-line.json @@ -0,0 +1,32 @@ +/** + * This configuration file defines custom commands for the "rush" command-line. + * For full documentation, please see https://rushjs.io/pages/configs/command_line_json/ + */ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/command-line.schema.json", + "commands": [ + { + "commandKind": "bulk", + "name": "format", + "summary": "Reformat source code in all projects", + "description": "Runs the `format` npm task in each project, if present.", + "safeForSimultaneousRushProcesses": false, + "enableParallelism": true, + "ignoreDependencyOrder": true, + "ignoreMissingScript": true, + "allowWarningsInSuccessfulBuild": false + } + ], + "parameters": [ + { + "parameterKind": "flag", + "longName": "--release", + "shortName": "-r", + "description": "Perform a release build", + "associatedCommands": [ + "build", + "rebuild" + ], + } + ] +} \ No newline at end of file diff --git a/common/config/rush/common-versions.json b/common/config/rush/common-versions.json new file mode 100644 index 000000000..d53e25a48 --- /dev/null +++ b/common/config/rush/common-versions.json @@ -0,0 +1,43 @@ +/** + * This configuration file specifies NPM dependency version selections that affect all projects + * in a Rush repo. For full documentation, please see https://rushjs.io + */ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/common-versions.schema.json", + + /** + * A table that specifies a "preferred version" for a dependency package. The "preferred version" + * is typically used to hold an indirect dependency back to a specific version, however generally + * it can be any SemVer range specifier (e.g. "~1.2.3"), and it will narrow any (compatible) + * SemVer range specifier. See the Rush documentation for details about this feature. + */ + "preferredVersions": { + + /** + * When someone asks for "^1.0.0" make sure they get "1.2.3" when working in this repo, + * instead of the latest version. + */ + // "some-library": "1.2.3" + }, + + /** + * The "rush check" command can be used to enforce that every project in the repo must specify + * the same SemVer range for a given dependency. However, sometimes exceptions are needed. + * The allowedAlternativeVersions table allows you to list other SemVer ranges that will be + * accepted by "rush check" for a given dependency. + * + * IMPORTANT: THIS TABLE IS FOR *ADDITIONAL* VERSION RANGES THAT ARE ALTERNATIVES TO THE + * USUAL VERSION (WHICH IS INFERRED BY LOOKING AT ALL PROJECTS IN THE REPO). + * This design avoids unnecessary churn in this file. + */ + "allowedAlternativeVersions": { + + /** + * For example, allow some projects to use an older TypeScript compiler + * (in addition to whatever "usual" version is being used by other projects in the repo): + */ + // "typescript": [ + // "~2.4.0" + // ] + } +} diff --git a/common/config/rush/pnpmfile.js b/common/config/rush/pnpmfile.js new file mode 100644 index 000000000..dc2d25544 --- /dev/null +++ b/common/config/rush/pnpmfile.js @@ -0,0 +1,32 @@ +"use strict"; + +/** + * When using the PNPM package manager, you can use pnpmfile.js to workaround + * dependencies that have mistakes in their package.json file. (This feature is + * functionally similar to Yarn's "resolutions".) + * + * For details, see the PNPM documentation: + * https://pnpm.js.org/docs/en/hooks.html + * + * IMPORTANT: SINCE THIS FILE CONTAINS EXECUTABLE CODE, MODIFYING IT IS LIKELY + * TO INVALIDATE ANY CACHED DEPENDENCY ANALYSIS. We recommend to run "rush update --full" + * after any modification to pnpmfile.js. + * + */ +module.exports = { + hooks: { + readPackage + } +}; + +/** + * This hook is invoked during installation before a package's dependencies + * are selected. + * The `packageJson` parameter is the deserialized package.json + * contents for the package that is about to be installed. + * The `context` parameter provides a log() function. + * The return value is the updated object. + */ +function readPackage(packageJson, context) { + return packageJson; +} diff --git a/common/config/rush/shrinkwrap.yaml b/common/config/rush/shrinkwrap.yaml new file mode 100644 index 000000000..0538a68fa --- /dev/null +++ b/common/config/rush/shrinkwrap.yaml @@ -0,0 +1,6646 @@ +dependencies: + '@microsoft/node-core-library': 3.13.0 + '@microsoft/rush-lib': 5.11.4 + '@rush-temp/build-tasks': 'file:projects/build-tasks.tgz' + '@rush-temp/semmle-bqrs': 'file:projects/semmle-bqrs.tgz' + '@rush-temp/semmle-io': 'file:projects/semmle-io.tgz' + '@rush-temp/semmle-io-node': 'file:projects/semmle-io-node.tgz' + '@rush-temp/semmle-vscode-utils': 'file:projects/semmle-vscode-utils.tgz' + '@rush-temp/typescript-config': 'file:projects/typescript-config.tgz' + '@rush-temp/vscode-codeql': 'file:projects/vscode-codeql.tgz' + '@types/chai': 4.1.7 + '@types/child-process-promise': 2.2.1 + '@types/classnames': 2.2.9 + '@types/fs-extra': 8.0.0 + '@types/glob': 7.1.1 + '@types/google-protobuf': 3.7.1 + '@types/gulp': 4.0.6 + '@types/js-yaml': 3.12.1 + '@types/jszip': 3.1.6 + '@types/mocha': 5.2.7 + '@types/node': 12.7.0 + '@types/node-fetch': 2.5.2 + '@types/npm-packlist': 1.1.1 + '@types/react': 16.8.25 + '@types/react-dom': 16.8.5 + '@types/sarif': 2.1.2 + '@types/through2': 2.0.34 + '@types/tmp': 0.1.0 + '@types/unzipper': 0.10.0 + '@types/vinyl': 2.0.3 + '@types/vscode': 1.39.0 + '@types/webpack': 4.32.1 + '@types/xml2js': 0.4.4 + ansi-colors: 4.1.1 + chai: 4.2.0 + child-process-promise: 2.2.1 + classnames: 2.2.6 + css-loader: 3.1.0 + fs-extra: 8.1.0 + glob: 7.1.4 + glob-promise: 3.4.0 + google-protobuf: 3.9.1 + gulp: 4.0.2 + gulp-sourcemaps: 2.6.5 + gulp-typescript: 5.0.1 + js-yaml: 3.13.1 + jsonc-parser: 2.1.0 + leb: 0.3.0 + mocha: 6.2.1 + node-fetch: 2.6.0 + npm-packlist: 1.4.4 + npm-run-all: 4.1.5 + plugin-error: 1.0.1 + react: 16.8.6 + react-dom: 16.8.6 + reflect-metadata: 0.1.13 + resolve: 1.11.1 + style-loader: 0.23.1 + through2: 3.0.1 + tmp: 0.1.0 + ts-loader: 5.4.5 + ts-node: 8.3.0 + ts-protoc-gen: 0.9.0 + typescript: 3.5.3 + typescript-formatter: 7.2.2 + unzipper: 0.10.5 + vinyl: 2.2.0 + vsce: 1.66.0 + vscode-jsonrpc: 4.0.0 + vscode-languageclient: 5.2.1 + vscode-test: 1.2.0 + webpack: 4.39.1 + webpack-cli: 3.3.6 + xml2js: 0.4.19 +packages: + /@gulp-sourcemaps/identity-map/1.0.2: + dependencies: + acorn: 5.7.3 + css: 2.2.4 + normalize-path: 2.1.1 + source-map: 0.6.1 + through2: 2.0.5 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha512-ciiioYMLdo16ShmfHBXJBOFm3xPC4AuwO4xeRpFeHz7WK9PYsWCmigagG2XyzZpubK4a3qNKoUBDhbzHfa50LQ== + /@gulp-sourcemaps/map-sources/1.0.0: + dependencies: + normalize-path: 2.1.1 + through2: 2.0.5 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-iQrnxdjId/bThIYCFazp1+yUW9o= + /@microsoft/node-core-library/3.13.0: + dependencies: + '@types/fs-extra': 5.0.4 + '@types/jju': 1.4.1 + '@types/node': 8.5.8 + '@types/z-schema': 3.16.31 + colors: 1.2.5 + fs-extra: 7.0.1 + jju: 1.4.0 + z-schema: 3.18.4 + dev: false + resolution: + integrity: sha512-mnsL/1ikVWHl8sPNssavaAgtUaIM3hkQ8zeySuApU5dNmsMPzovJPfx9m5JGiMvs1v5QNAIVeiS9jnWwe/7anw== + /@microsoft/node-core-library/3.14.0: + dependencies: + '@types/fs-extra': 5.0.4 + '@types/jju': 1.4.1 + '@types/node': 8.5.8 + '@types/z-schema': 3.16.31 + colors: 1.2.5 + fs-extra: 7.0.1 + jju: 1.4.0 + z-schema: 3.18.4 + dev: false + resolution: + integrity: sha512-+gbTXTRfvR40hTH+C3Vno/RJ51sU/RZAyHb2bo9af8GCdOgxCxCs+qp2KCXklbpuolmIPFfbCmdTwv90yH5tJw== + /@microsoft/package-deps-hash/2.2.170: + dev: false + resolution: + integrity: sha512-dUkeTu0t4L4i9An96E5iPgvYhhqdtdx3dQP9qlpqb+suCFfvdoftDD4lC0euk3yCwoAQB6LVGdPkE4Y6vSQgkg== + /@microsoft/rush-lib/5.11.4: + dependencies: + '@microsoft/node-core-library': 3.14.0 + '@microsoft/package-deps-hash': 2.2.170 + '@microsoft/stream-collator': 3.0.85 + '@microsoft/ts-command-line': 4.2.7 + '@pnpm/link-bins': /@pnpm/link-bins/1.0.3/@pnpm!logger@1.0.2 + '@pnpm/logger': 1.0.2 + '@types/inquirer': 0.0.43 + '@yarnpkg/lockfile': 1.0.2 + builtins: 1.0.3 + cli-table: 0.3.1 + colors: 1.2.5 + git-repo-info: 2.1.0 + glob: 7.0.6 + glob-escape: 0.0.2 + https-proxy-agent: 2.2.2 + inquirer: 6.2.2 + js-yaml: 3.13.1 + lodash: 4.17.15 + minimatch: 3.0.4 + node-fetch: 2.1.2 + npm-package-arg: 5.1.2 + read-package-tree: 5.1.6 + semver: 5.3.0 + strict-uri-encode: 2.0.0 + tar: 4.4.13 + true-case-path: 2.2.1 + wordwrap: 1.0.0 + z-schema: 3.18.4 + dev: false + engines: + node: '>=5.6.0' + resolution: + integrity: sha512-RbgrTWr2W9YBwwKghzlxZFSU645sh4U004X529osmDglOt2H2SPXIlWT7lZw80yXhsUEHjxCmSfbESPnAMXAwA== + /@microsoft/stream-collator/3.0.85: + dependencies: + '@types/node': 8.5.8 + colors: 1.2.5 + dev: false + resolution: + integrity: sha512-txjs1YzUTk1zZZClNEaXZ6J5T0txlTE6yjNxrSX5qpZm20iYIJamHlccqMFEWhkUtc8uscSPDSynm/mvieQTdw== + /@microsoft/ts-command-line/4.2.7: + dependencies: + '@types/argparse': 1.0.33 + '@types/node': 8.5.8 + argparse: 1.0.10 + colors: 1.2.5 + dev: false + resolution: + integrity: sha512-PwUMIIDl8oWyl64Y5DW5FAuoRk4KWTBZdk4FEh366KEm5xYFBQhCeatHGURIj8nEYm0Xb2coCrXF77dGDlp/Qw== + /@pnpm/link-bins/1.0.3/@pnpm!logger@1.0.2: + dependencies: + '@pnpm/logger': 1.0.2 + '@pnpm/package-bins': 1.0.0 + '@pnpm/types': 1.8.0 + '@types/mz': 0.0.32 + '@types/node': 10.14.21 + '@types/ramda': 0.25.51 + '@zkochan/cmd-shim': 2.2.4 + arr-flatten: 1.1.0 + is-windows: 1.0.2 + mkdirp-promise: 5.0.1 + mz: 2.7.0 + normalize-path: 3.0.0 + p-filter: 1.0.0 + ramda: 0.25.0 + read-package-json: 2.1.0 + dev: false + engines: + node: '>=4' + id: registry.npmjs.org/@pnpm/link-bins/1.0.3 + peerDependencies: + '@pnpm/logger': ^1.0.0 + resolution: + integrity: sha512-thVgwrQ5rMcPYI6a0IPOt2pnlF1n5zX7BN4CrFeBp0/JCGsZAht/VOPv9bD3cZ+j0vDemEwE23BfhOWxmxq2yQ== + /@pnpm/logger/1.0.2: + dependencies: + '@types/node': 10.14.21 + bole: 3.0.2 + ndjson: 1.5.0 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha512-A8XbJKvdueazvJGPn1qQ9LL6uopV88ebIT+dJKNQ68gT7yfCbtfT8j5ZzdVczmGbkiuBeZ1VckZerkO0tjOXZA== + /@pnpm/package-bins/1.0.0: + dependencies: + '@pnpm/types': 1.8.0 + '@types/mz': 0.0.32 + mz: 2.7.0 + p-filter: 1.0.0 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha512-ZqVfIXK3r5AsP5VAhPHrhf3isF+T4yEuUpJTF9T03oFTJ9LBnkKvx8F7P7biKEManxSGOkSpNoIBdsura9pY5Q== + /@pnpm/types/1.8.0: + dev: false + resolution: + integrity: sha512-NsEzBVa5aMgn/n79piyJtpUQFzJ97tB2R2r8PSJlLnMA6LJmchKuv7ATN+/nZH/3QRd/+uFXEq07/i/ajsqVGQ== + /@types/anymatch/1.3.1: + dev: false + resolution: + integrity: sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA== + /@types/argparse/1.0.33: + dev: false + resolution: + integrity: sha512-VQgHxyPMTj3hIlq9SY1mctqx+Jj8kpQfoLvDlVSDNOyuYs8JYfkuY3OW/4+dO657yPmNhHpePRx0/Tje5ImNVQ== + /@types/chai/4.1.7: + dev: false + resolution: + integrity: sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA== + /@types/child-process-promise/2.2.1: + dependencies: + '@types/node': 12.7.0 + dev: false + resolution: + integrity: sha512-xZ4kkF82YkmqPCERqV9Tj0bVQj3Tk36BqGlNgxv5XhifgDRhwAqp+of+sccksdpZRbbPsNwMOkmUqOnLgxKtGw== + /@types/classnames/2.2.9: + dev: false + resolution: + integrity: sha512-MNl+rT5UmZeilaPxAVs6YaPC2m6aA8rofviZbhbxpPpl61uKodfdQVsBtgJGTqGizEf02oW3tsVe7FYB8kK14A== + /@types/events/3.0.0: + dev: false + resolution: + integrity: sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== + /@types/fs-extra/5.0.4: + dependencies: + '@types/node': 12.7.0 + dev: false + resolution: + integrity: sha512-DsknoBvD8s+RFfSGjmERJ7ZOP1HI0UZRA3FSI+Zakhrc/Gy26YQsLI+m5V5DHxroHRJqCDLKJp7Hixn8zyaF7g== + /@types/fs-extra/8.0.0: + dependencies: + '@types/node': 12.7.0 + dev: false + resolution: + integrity: sha512-bCtL5v9zdbQW86yexOlXWTEGvLNqWxMFyi7gQA7Gcthbezr2cPSOb8SkESVKA937QD5cIwOFLDFt0MQoXOEr9Q== + /@types/glob-stream/6.1.0: + dependencies: + '@types/glob': 7.1.1 + '@types/node': 12.7.0 + dev: false + resolution: + integrity: sha512-RHv6ZQjcTncXo3thYZrsbAVwoy4vSKosSWhuhuQxLOTv74OJuFQxXkmUuZCr3q9uNBEVCvIzmZL/FeRNbHZGUg== + /@types/glob/7.1.1: + dependencies: + '@types/events': 3.0.0 + '@types/minimatch': 3.0.3 + '@types/node': 12.7.0 + dev: false + resolution: + integrity: sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== + /@types/google-protobuf/3.7.1: + dev: false + resolution: + integrity: sha512-kiLxbqoi2C7NmkGj1ZpkSDyIqj4vqDEIjx7wX+O0GXV6bLX6u/oLz49CwefD0c0vzaKeBdOqmUtI8bC0bBRr0w== + /@types/gulp/4.0.6: + dependencies: + '@types/undertaker': 1.2.2 + '@types/vinyl-fs': 2.4.11 + chokidar: 2.1.6 + dev: false + resolution: + integrity: sha512-0E8/iV/7FKWyQWSmi7jnUvgXXgaw+pfAzEB06Xu+l0iXVJppLbpOye5z7E2klw5akXd+8kPtYuk65YBcZPM4ow== + /@types/inquirer/0.0.43: + dependencies: + '@types/rx': 4.1.1 + '@types/through': 0.0.29 + dev: false + resolution: + integrity: sha512-xgyfKZVMFqE8aIKy1xfFVsX2MxyXUNgjgmbF6dRbR3sL+ZM5K4ka/9L4mmTwX8eTeVYtduyXu0gUVwVJa1HbNw== + /@types/jju/1.4.1: + dev: false + resolution: + integrity: sha512-LFt+YA7Lv2IZROMwokZKiPNORAV5N3huMs3IKnzlE430HWhWYZ8b+78HiwJXJJP1V2IEjinyJURuRJfGoaFSIA== + /@types/js-yaml/3.12.1: + dev: false + resolution: + integrity: sha512-SGGAhXLHDx+PK4YLNcNGa6goPf9XRWQNAUUbffkwVGGXIxmDKWyGGL4inzq2sPmExu431Ekb9aEMn9BkPqEYFA== + /@types/jszip/3.1.6: + dependencies: + '@types/node': 12.7.1 + dev: false + resolution: + integrity: sha512-m8uFcI+O2EupCfbEVQWsBM/4nhbegjOHL7cQgBpM95FeF98kdFJXzy9/8yhx4b3lCRl/gMBhcvyh30Qt3X+XPQ== + /@types/minimatch/3.0.3: + dev: false + resolution: + integrity: sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== + /@types/mocha/5.2.7: + dev: false + resolution: + integrity: sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ== + /@types/mz/0.0.32: + dependencies: + '@types/node': 12.7.12 + dev: false + resolution: + integrity: sha512-cy3yebKhrHuOcrJGkfwNHhpTXQLgmXSv1BX+4p32j+VUQ6aP2eJ5cL7OvGcAQx75fCTFaAIIAKewvqL+iwSd4g== + /@types/node-fetch/2.5.2: + dependencies: + '@types/node': 12.11.2 + dev: false + resolution: + integrity: sha512-djYYKmdNRSBtL1x4CiE9UJb9yZhwtI1VC+UxZD0psNznrUj80ywsxKlEGAE+QL1qvLjPbfb24VosjkYM6W4RSQ== + /@types/node/10.14.21: + dev: false + resolution: + integrity: sha512-nuFlRdBiqbF+PJIEVxm2jLFcQWN7q7iWEJGsBV4n7v1dbI9qXB8im2pMMKMCUZe092sQb5SQft2DHfuQGK5hqQ== + /@types/node/12.11.2: + dev: false + resolution: + integrity: sha512-dsfE4BHJkLQW+reOS6b17xhZ/6FB1rB8eRRvO08nn5o+voxf3i74tuyFWNH6djdfgX7Sm5s6LD8t6mJug4dpDw== + /@types/node/12.7.0: + dev: false + resolution: + integrity: sha512-vqcj1MVm2Sla4PpMfYKh1MyDN4D2f/mPIZD7RdAGqEsbE+JxfeqQHHVbRDQ0Nqn8i73gJa1HQ1Pu3+nH4Q0Yiw== + /@types/node/12.7.1: + dev: false + resolution: + integrity: sha512-aK9jxMypeSrhiYofWWBf/T7O+KwaiAHzM4sveCdWPn71lzUSMimRnKzhXDKfKwV1kWoBo2P1aGgaIYGLf9/ljw== + /@types/node/12.7.12: + dev: false + resolution: + integrity: sha512-KPYGmfD0/b1eXurQ59fXD1GBzhSQfz6/lKBxkaHX9dKTzjXbK68Zt7yGUxUsCS1jeTy/8aL+d9JEr+S54mpkWQ== + /@types/node/12.7.2: + dev: false + resolution: + integrity: sha512-dyYO+f6ihZEtNPDcWNR1fkoTDf3zAK3lAABDze3mz6POyIercH0lEUawUFXlG8xaQZmm1yEBON/4TsYv/laDYg== + /@types/node/12.7.5: + dev: false + resolution: + integrity: sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w== + /@types/node/8.5.8: + dev: false + resolution: + integrity: sha512-8KmlRxwbKZfjUHFIt3q8TF5S2B+/E5BaAoo/3mgc5h6FJzqxXkCK/VMetO+IRDtwtU6HUvovHMBn+XRj7SV9Qg== + /@types/npm-packlist/1.1.1: + dev: false + resolution: + integrity: sha512-+0ZRUpPOs4Mvvwj/pftWb14fnPN/yS6nOp6HZFyIMDuUmyPtKXcO4/SPhyRGR6dUCAn1B3hHJozD/UCrU+Mmew== + /@types/prop-types/15.7.1: + dev: false + resolution: + integrity: sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg== + /@types/ramda/0.25.51: + dev: false + resolution: + integrity: sha512-xcmtfHIgF9SYjhGdsZR1nQslxG4hu0cIpFfLQ4CWdw3KzHvl7ki1AzFLQUkbDTG42ZN3ZsQfdRzXRlkAvbIy5Q== + /@types/react-dom/16.8.5: + dependencies: + '@types/react': 16.8.25 + dev: false + resolution: + integrity: sha512-idCEjROZ2cqh29+trmTmZhsBAUNQuYrF92JHKzZ5+aiFM1mlSk3bb23CK7HhYuOY75Apgap5y2jTyHzaM2AJGA== + /@types/react/16.8.25: + dependencies: + '@types/prop-types': 15.7.1 + csstype: 2.6.6 + dev: false + resolution: + integrity: sha512-ydAAkLnNTC4oYSxJ3zwK/4QcVmEecACJ4ZdxXITbxz/dhahBSDKY6OQ1uawAW6rE/7kfHccxulYLSAIZVrSq0A== + /@types/rx-core-binding/4.0.4: + dependencies: + '@types/rx-core': 4.0.3 + dev: false + resolution: + integrity: sha512-5pkfxnC4w810LqBPUwP5bg7SFR/USwhMSaAeZQQbEHeBp57pjKXRlXmqpMrLJB4y1oglR/c2502853uN0I+DAQ== + /@types/rx-core/4.0.3: + dev: false + resolution: + integrity: sha1-CzNUsSOM7b4rdPYybxOdvHpZHWA= + /@types/rx-lite-aggregates/4.0.3: + dependencies: + '@types/rx-lite': 4.0.6 + dev: false + resolution: + integrity: sha512-MAGDAHy8cRatm94FDduhJF+iNS5//jrZ/PIfm+QYw9OCeDgbymFHChM8YVIvN2zArwsRftKgE33QfRWvQk4DPg== + /@types/rx-lite-async/4.0.2: + dependencies: + '@types/rx-lite': 4.0.6 + dev: false + resolution: + integrity: sha512-vTEv5o8l6702ZwfAM5aOeVDfUwBSDOs+ARoGmWAKQ6LOInQ8J4/zjM7ov12fuTpktUKdMQjkeCp07Vd73mPkxw== + /@types/rx-lite-backpressure/4.0.3: + dependencies: + '@types/rx-lite': 4.0.6 + dev: false + resolution: + integrity: sha512-Y6aIeQCtNban5XSAF4B8dffhIKu6aAy/TXFlScHzSxh6ivfQBQw6UjxyEJxIOt3IT49YkS+siuayM2H/Q0cmgA== + /@types/rx-lite-coincidence/4.0.3: + dependencies: + '@types/rx-lite': 4.0.6 + dev: false + resolution: + integrity: sha512-1VNJqzE9gALUyMGypDXZZXzR0Tt7LC9DdAZQ3Ou/Q0MubNU35agVUNXKGHKpNTba+fr8GdIdkC26bRDqtCQBeQ== + /@types/rx-lite-experimental/4.0.1: + dependencies: + '@types/rx-lite': 4.0.6 + dev: false + resolution: + integrity: sha1-xTL1y98/LBXaFt7Ykw0bKYQCPL0= + /@types/rx-lite-joinpatterns/4.0.1: + dependencies: + '@types/rx-lite': 4.0.6 + dev: false + resolution: + integrity: sha1-9w/jcFGKhDLykVjMkv+1a05K/D4= + /@types/rx-lite-testing/4.0.1: + dependencies: + '@types/rx-lite-virtualtime': 4.0.3 + dev: false + resolution: + integrity: sha1-IbGdEfTf1v/vWp0WSOnIh5v+Iek= + /@types/rx-lite-time/4.0.3: + dependencies: + '@types/rx-lite': 4.0.6 + dev: false + resolution: + integrity: sha512-ukO5sPKDRwCGWRZRqPlaAU0SKVxmWwSjiOrLhoQDoWxZWg6vyB9XLEZViKOzIO6LnTIQBlk4UylYV0rnhJLxQw== + /@types/rx-lite-virtualtime/4.0.3: + dependencies: + '@types/rx-lite': 4.0.6 + dev: false + resolution: + integrity: sha512-3uC6sGmjpOKatZSVHI2xB1+dedgml669ZRvqxy+WqmGJDVusOdyxcKfyzjW0P3/GrCiN4nmRkLVMhPwHCc5QLg== + /@types/rx-lite/4.0.6: + dependencies: + '@types/rx-core': 4.0.3 + '@types/rx-core-binding': 4.0.4 + dev: false + resolution: + integrity: sha512-oYiDrFIcor9zDm0VDUca1UbROiMYBxMLMaM6qzz4ADAfOmA9r1dYEcAFH+2fsPI5BCCjPvV9pWC3X3flbrvs7w== + /@types/rx/4.1.1: + dependencies: + '@types/rx-core': 4.0.3 + '@types/rx-core-binding': 4.0.4 + '@types/rx-lite': 4.0.6 + '@types/rx-lite-aggregates': 4.0.3 + '@types/rx-lite-async': 4.0.2 + '@types/rx-lite-backpressure': 4.0.3 + '@types/rx-lite-coincidence': 4.0.3 + '@types/rx-lite-experimental': 4.0.1 + '@types/rx-lite-joinpatterns': 4.0.1 + '@types/rx-lite-testing': 4.0.1 + '@types/rx-lite-time': 4.0.3 + '@types/rx-lite-virtualtime': 4.0.3 + dev: false + resolution: + integrity: sha1-WY/JSla67ZdfGUV04PVy/Y5iekg= + /@types/sarif/2.1.2: + dev: false + resolution: + integrity: sha512-TELZl5h48KaB6SFZqTuaMEw1hrGuusbBcH+yfMaaHdS2pwDr3RTH4CVN0LyY1kqSiDm9PPvAMx8FJ0LUZreOCQ== + /@types/tapable/1.0.4: + dev: false + resolution: + integrity: sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ== + /@types/through/0.0.29: + dependencies: + '@types/node': 12.7.12 + dev: false + resolution: + integrity: sha512-9a7C5VHh+1BKblaYiq+7Tfc+EOmjMdZaD1MYtkQjSoxgB69tBjW98ry6SKsi4zEIWztLOMRuL87A3bdT/Fc/4w== + /@types/through2/2.0.34: + dependencies: + '@types/node': 12.7.0 + dev: false + resolution: + integrity: sha512-nhRG8+RuG/L+0fAZBQYaRflXKjTrHOKH8MFTChnf+dNVMxA3wHYYrfj0tztK0W51ABXjGfRCDc0vRkecCOrsow== + /@types/tmp/0.1.0: + dev: false + resolution: + integrity: sha512-6IwZ9HzWbCq6XoQWhxLpDjuADodH/MKXRUIDFudvgjcVdjFknvmR+DNsoUeer4XPrEnrZs04Jj+kfV9pFsrhmA== + /@types/uglify-js/3.0.4: + dependencies: + source-map: 0.6.1 + dev: false + resolution: + integrity: sha512-SudIN9TRJ+v8g5pTG8RRCqfqTMNqgWCKKd3vtynhGzkIIjxaicNAMuY5TRadJ6tzDu3Dotf3ngaMILtmOdmWEQ== + /@types/undertaker-registry/1.0.1: + dev: false + resolution: + integrity: sha512-Z4TYuEKn9+RbNVk1Ll2SS4x1JeLHecolIbM/a8gveaHsW0Hr+RQMraZACwTO2VD7JvepgA6UO1A1VrbktQrIbQ== + /@types/undertaker/1.2.2: + dependencies: + '@types/undertaker-registry': 1.0.1 + dev: false + resolution: + integrity: sha512-j4iepCSuY2JGW/hShVtUBagic0klYNFIXP7VweavnYnNC2EjiKxJFeaS9uaJmAT0ty9sQSqTS1aagWMZMV0HyA== + /@types/unzipper/0.10.0: + dependencies: + '@types/node': 12.11.2 + dev: false + resolution: + integrity: sha512-GZL5vt0o9ZAST+7ge1Sirzc14EEJFbq6kib24nS0UglY6BHX8ERhA8cBq4XsYWcGK212FtMBZyJz6AwPvrhGLQ== + /@types/vinyl-fs/2.4.11: + dependencies: + '@types/glob-stream': 6.1.0 + '@types/node': 12.7.0 + '@types/vinyl': 2.0.3 + dev: false + resolution: + integrity: sha512-2OzQSfIr9CqqWMGqmcERE6Hnd2KY3eBVtFaulVo3sJghplUcaeMdL9ZjEiljcQQeHjheWY9RlNmumjIAvsBNaA== + /@types/vinyl/2.0.3: + dependencies: + '@types/node': 12.7.0 + dev: false + resolution: + integrity: sha512-hrT6xg16CWSmndZqOTJ6BGIn2abKyTw0B58bI+7ioUoj3Sma6u8ftZ1DTI2yCaJamOVGLOnQWiPH3a74+EaqTA== + /@types/vscode/1.39.0: + dev: false + resolution: + integrity: sha512-rlg0okXDt7NjAyHXbZ2nO1I/VY/8y9w67ltLRrOxXQ46ayvrYZavD4A6zpYrGbs2+ZOEQzcUs+QZOqcVGQIxXQ== + /@types/webpack/4.32.1: + dependencies: + '@types/anymatch': 1.3.1 + '@types/node': 12.7.0 + '@types/tapable': 1.0.4 + '@types/uglify-js': 3.0.4 + source-map: 0.6.1 + dev: false + resolution: + integrity: sha512-9n38CBx9uga1FEAdTipnt0EkbKpsCJFh7xJb1LE65FFb/A6OOLFX022vYsGC1IyVCZ/GroNg9u/RMmlDxGcLIw== + /@types/xml2js/0.4.4: + dependencies: + '@types/node': 12.7.2 + dev: false + resolution: + integrity: sha512-O6Xgai01b9PB3IGA0lRIp1Ex3JBcxGDhdO0n3NIIpCyDOAjxcIGQFmkvgJpP8anTrthxOUQjBfLdRRi0Zn/TXA== + /@types/z-schema/3.16.31: + dev: false + resolution: + integrity: sha1-LrHQCl5Ow/pYx2r94S4YK2bcXBw= + /@webassemblyjs/ast/1.8.5: + dependencies: + '@webassemblyjs/helper-module-context': 1.8.5 + '@webassemblyjs/helper-wasm-bytecode': 1.8.5 + '@webassemblyjs/wast-parser': 1.8.5 + dev: false + resolution: + integrity: sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ== + /@webassemblyjs/floating-point-hex-parser/1.8.5: + dev: false + resolution: + integrity: sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ== + /@webassemblyjs/helper-api-error/1.8.5: + dev: false + resolution: + integrity: sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA== + /@webassemblyjs/helper-buffer/1.8.5: + dev: false + resolution: + integrity: sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q== + /@webassemblyjs/helper-code-frame/1.8.5: + dependencies: + '@webassemblyjs/wast-printer': 1.8.5 + dev: false + resolution: + integrity: sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ== + /@webassemblyjs/helper-fsm/1.8.5: + dev: false + resolution: + integrity: sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow== + /@webassemblyjs/helper-module-context/1.8.5: + dependencies: + '@webassemblyjs/ast': 1.8.5 + mamacro: 0.0.3 + dev: false + resolution: + integrity: sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g== + /@webassemblyjs/helper-wasm-bytecode/1.8.5: + dev: false + resolution: + integrity: sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ== + /@webassemblyjs/helper-wasm-section/1.8.5: + dependencies: + '@webassemblyjs/ast': 1.8.5 + '@webassemblyjs/helper-buffer': 1.8.5 + '@webassemblyjs/helper-wasm-bytecode': 1.8.5 + '@webassemblyjs/wasm-gen': 1.8.5 + dev: false + resolution: + integrity: sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA== + /@webassemblyjs/ieee754/1.8.5: + dependencies: + '@xtuc/ieee754': 1.2.0 + dev: false + resolution: + integrity: sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g== + /@webassemblyjs/leb128/1.8.5: + dependencies: + '@xtuc/long': 4.2.2 + dev: false + resolution: + integrity: sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A== + /@webassemblyjs/utf8/1.8.5: + dev: false + resolution: + integrity: sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw== + /@webassemblyjs/wasm-edit/1.8.5: + dependencies: + '@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 + dev: false + resolution: + integrity: sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q== + /@webassemblyjs/wasm-gen/1.8.5: + dependencies: + '@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 + dev: false + resolution: + integrity: sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg== + /@webassemblyjs/wasm-opt/1.8.5: + dependencies: + '@webassemblyjs/ast': 1.8.5 + '@webassemblyjs/helper-buffer': 1.8.5 + '@webassemblyjs/wasm-gen': 1.8.5 + '@webassemblyjs/wasm-parser': 1.8.5 + dev: false + resolution: + integrity: sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q== + /@webassemblyjs/wasm-parser/1.8.5: + dependencies: + '@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 + dev: false + resolution: + integrity: sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw== + /@webassemblyjs/wast-parser/1.8.5: + dependencies: + '@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 + dev: false + resolution: + integrity: sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg== + /@webassemblyjs/wast-printer/1.8.5: + dependencies: + '@webassemblyjs/ast': 1.8.5 + '@webassemblyjs/wast-parser': 1.8.5 + '@xtuc/long': 4.2.2 + dev: false + resolution: + integrity: sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg== + /@xtuc/ieee754/1.2.0: + dev: false + resolution: + integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + /@xtuc/long/4.2.2: + dev: false + resolution: + integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + /@yarnpkg/lockfile/1.0.2: + dev: false + resolution: + integrity: sha512-MqJ00WXw89ga0rK6GZkdmmgv3bAsxpJixyTthjcix73O44pBqotyU2BejBkLuIsaOBI6SEu77vAnSyLe5iIHkw== + /@zkochan/cmd-shim/2.2.4: + dependencies: + is-windows: 1.0.2 + mkdirp-promise: 5.0.1 + mz: 2.7.0 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha512-BDy1oz6aFYyY73618IkXzJzFghnXwVZDc3SVa6MVKTrrk4RgubahAF5yKK+Mx4a78tfO0OHeZnJKPs0pNy5uNA== + /acorn/5.7.3: + dev: false + engines: + node: '>=0.4.0' + hasBin: true + resolution: + integrity: sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== + /acorn/6.2.1: + dev: false + engines: + node: '>=0.4.0' + hasBin: true + resolution: + integrity: sha512-JD0xT5FCRDNyjDda3Lrg/IxFscp9q4tiYtxE1/nOzlKCk7hIRuYjhq1kCNkbPjMRMZuFq20HNQn1I9k8Oj0E+Q== + /agent-base/4.3.0: + dependencies: + es6-promisify: 5.0.0 + dev: false + engines: + node: '>= 4.0.0' + resolution: + integrity: sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== + /ajv-errors/1.0.1/ajv@6.10.2: + dependencies: + ajv: 6.10.2 + dev: false + id: registry.npmjs.org/ajv-errors/1.0.1 + peerDependencies: + ajv: '>=5.0.0' + resolution: + integrity: sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== + /ajv-keywords/3.4.1/ajv@6.10.2: + dependencies: + ajv: 6.10.2 + dev: false + id: registry.npmjs.org/ajv-keywords/3.4.1 + peerDependencies: + ajv: ^6.9.1 + resolution: + integrity: sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== + /ajv/6.10.2: + dependencies: + fast-deep-equal: 2.0.1 + fast-json-stable-stringify: 2.0.0 + json-schema-traverse: 0.4.1 + uri-js: 4.2.2 + dev: false + resolution: + integrity: sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw== + /ansi-colors/1.1.0: + dependencies: + ansi-wrap: 0.1.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA== + /ansi-colors/3.2.3: + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== + /ansi-colors/3.2.4: + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== + /ansi-colors/4.1.1: + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + /ansi-escapes/3.2.0: + dev: false + engines: + node: '>=4' + resolution: + integrity: sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== + /ansi-gray/0.1.1: + dependencies: + ansi-wrap: 0.1.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-KWLPVOyXksSFEKPetSRDaGHvclE= + /ansi-regex/2.1.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + /ansi-regex/3.0.0: + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + /ansi-regex/4.1.0: + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + /ansi-styles/3.2.1: + dependencies: + color-convert: 1.9.3 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + /ansi-wrap/0.1.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-qCJQ3bABXponyoLoLqYDu/pF768= + /any-promise/1.3.0: + dev: false + resolution: + integrity: sha1-q8av7tzqUugJzcA3au0845Y10X8= + /anymatch/2.0.0: + dependencies: + micromatch: 3.1.10 + normalize-path: 2.1.1 + dev: false + resolution: + integrity: sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + /append-buffer/1.0.2: + dependencies: + buffer-equal: 1.0.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE= + /aproba/1.2.0: + dev: false + resolution: + integrity: sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + /archy/1.0.0: + dev: false + resolution: + integrity: sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= + /arg/4.1.1: + dev: false + resolution: + integrity: sha512-SlmP3fEA88MBv0PypnXZ8ZfJhwmDeIE3SP71j37AiXQBXYosPV0x6uISAaHYSlSVhmHOVkomen0tbGk6Anlebw== + /argparse/1.0.10: + dependencies: + sprintf-js: 1.0.3 + dev: false + resolution: + integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + /arr-diff/4.0.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + /arr-filter/1.1.2: + dependencies: + make-iterator: 1.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4= + /arr-flatten/1.1.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + /arr-map/2.0.2: + dependencies: + make-iterator: 1.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-Onc0X/wc814qkYJWAfnljy4kysQ= + /arr-union/3.1.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + /array-each/1.0.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-p5SvDAWrF1KEbudTofIRoFugxE8= + /array-filter/0.0.1: + dev: false + resolution: + integrity: sha1-fajPLiZijtcygDWB/SH2fKzS7uw= + /array-initial/1.1.0: + dependencies: + array-slice: 1.1.0 + is-number: 4.0.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-L6dLJnOTccOUe9enrcc74zSz15U= + /array-last/1.3.0: + dependencies: + is-number: 4.0.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg== + /array-map/0.0.0: + dev: false + resolution: + integrity: sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI= + /array-reduce/0.0.0: + dev: false + resolution: + integrity: sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys= + /array-slice/1.1.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w== + /array-sort/1.0.0: + dependencies: + default-compare: 1.0.0 + get-value: 2.0.6 + kind-of: 5.1.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg== + /array-unique/0.3.2: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + /asap/2.0.6: + dev: false + resolution: + integrity: sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= + /asn1.js/4.10.1: + dependencies: + bn.js: 4.11.8 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + dev: false + resolution: + integrity: sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== + /assert/1.5.0: + dependencies: + object-assign: 4.1.1 + util: 0.10.3 + dev: false + resolution: + integrity: sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== + /assertion-error/1.1.0: + dev: false + resolution: + integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== + /assign-symbols/1.0.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + /async-done/1.3.2: + dependencies: + end-of-stream: 1.4.1 + once: 1.4.0 + process-nextick-args: 2.0.1 + stream-exhaust: 1.0.2 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw== + /async-each/1.0.3: + dev: false + resolution: + integrity: sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== + /async-settle/1.0.0: + dependencies: + async-done: 1.3.2 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs= + /atob/2.1.2: + dev: false + engines: + node: '>= 4.5.0' + hasBin: true + resolution: + integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + /azure-devops-node-api/7.2.0: + dependencies: + os: 0.1.1 + tunnel: 0.0.4 + typed-rest-client: 1.2.0 + underscore: 1.8.3 + dev: false + resolution: + integrity: sha512-pMfGJ6gAQ7LRKTHgiRF+8iaUUeGAI0c8puLaqHLc7B8AR7W6GJLozK9RFeUHFjEGybC9/EB3r67WPd7e46zQ8w== + /bach/1.2.0: + dependencies: + arr-filter: 1.1.2 + arr-flatten: 1.1.0 + arr-map: 2.0.2 + array-each: 1.0.1 + array-initial: 1.1.0 + array-last: 1.3.0 + async-done: 1.3.2 + async-settle: 1.0.0 + now-and-later: 2.0.1 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA= + /balanced-match/1.0.0: + dev: false + resolution: + integrity: sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + /base/0.11.2: + dependencies: + 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 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + /base64-js/1.3.1: + dev: false + resolution: + integrity: sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== + /big-integer/1.6.47: + dev: false + engines: + node: '>=0.6' + resolution: + integrity: sha512-9t9f7X3as2XGX8b52GqG6ox0GvIdM86LyIXASJnDCFhYNgt+A+MByQZ3W2PyMRZjEvG5f8TEbSPfEotVuMJnQg== + /big.js/5.2.2: + dev: false + resolution: + integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + /binary-extensions/1.13.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== + /binary/0.3.0: + dependencies: + buffers: 0.1.1 + chainsaw: 0.1.0 + dev: false + resolution: + integrity: sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk= + /bluebird/3.4.7: + dev: false + resolution: + integrity: sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM= + /bluebird/3.5.5: + dev: false + resolution: + integrity: sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w== + /bn.js/4.11.8: + dev: false + resolution: + integrity: sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== + /bole/3.0.2: + dependencies: + fast-safe-stringify: 1.1.13 + individual: 3.0.0 + dev: false + resolution: + integrity: sha1-vIpIPKlASdqbg3wa0Rzf6+5uBRQ= + /boolbase/1.0.0: + dev: false + resolution: + integrity: sha1-aN/1++YMUes3cl6p4+0xDcwed24= + /brace-expansion/1.1.11: + dependencies: + balanced-match: 1.0.0 + concat-map: 0.0.1 + dev: false + resolution: + integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + /braces/2.3.2: + dependencies: + 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 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + /brorand/1.1.0: + dev: false + resolution: + integrity: sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= + /browser-stdout/1.3.1: + dev: false + resolution: + integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + /browserify-aes/1.2.0: + dependencies: + 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.2.0 + dev: false + resolution: + integrity: sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== + /browserify-cipher/1.0.1: + dependencies: + browserify-aes: 1.2.0 + browserify-des: 1.0.2 + evp_bytestokey: 1.0.3 + dev: false + resolution: + integrity: sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== + /browserify-des/1.0.2: + dependencies: + cipher-base: 1.0.4 + des.js: 1.0.0 + inherits: 2.0.4 + safe-buffer: 5.2.0 + dev: false + resolution: + integrity: sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== + /browserify-rsa/4.0.1: + dependencies: + bn.js: 4.11.8 + randombytes: 2.1.0 + dev: false + resolution: + integrity: sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= + /browserify-sign/4.0.4: + dependencies: + 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 + dev: false + resolution: + integrity: sha1-qk62jl17ZYuqa/alfmMMvXqT0pg= + /browserify-zlib/0.2.0: + dependencies: + pako: 1.0.10 + dev: false + resolution: + integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== + /buffer-crc32/0.2.13: + dev: false + resolution: + integrity: sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= + /buffer-equal/1.0.0: + dev: false + engines: + node: '>=0.4.0' + resolution: + integrity: sha1-WWFrSYME1Var1GaWayLu2j7KX74= + /buffer-from/1.1.1: + dev: false + resolution: + integrity: sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + /buffer-indexof-polyfill/1.0.1: + dev: false + engines: + node: '>=0.10' + resolution: + integrity: sha1-qfuAbOgUXVQoUQznLyeLs2OmOL8= + /buffer-xor/1.0.3: + dev: false + resolution: + integrity: sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= + /buffer/4.9.1: + dependencies: + base64-js: 1.3.1 + ieee754: 1.1.13 + isarray: 1.0.0 + dev: false + resolution: + integrity: sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg= + /buffers/0.1.1: + dev: false + engines: + node: '>=0.2.0' + resolution: + integrity: sha1-skV5w77U1tOWru5tmorn9Ugqt7s= + /builtin-status-codes/3.0.0: + dev: false + resolution: + integrity: sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= + /builtins/1.0.3: + dev: false + resolution: + integrity: sha1-y5T662HIaWRR2zZTThQi+U8K7og= + /cacache/12.0.2: + dependencies: + bluebird: 3.5.5 + chownr: 1.1.2 + figgy-pudding: 3.5.1 + glob: 7.1.4 + graceful-fs: 4.2.1 + infer-owner: 1.0.4 + 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 + dev: false + resolution: + integrity: sha512-ifKgxH2CKhJEg6tNdAwziu6Q33EvuG26tYcda6PT3WKisZcYDXsnEdnRv67Po3yCzFfaSoMjGZzJyD2c3DT1dg== + /cache-base/1.0.1: + dependencies: + 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 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + /camelcase/3.0.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-MvxLn82vhF/N9+c7uXysImHwqwo= + /camelcase/5.3.1: + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + /chai/4.2.0: + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.2 + deep-eql: 3.0.1 + get-func-name: 2.0.0 + pathval: 1.1.0 + type-detect: 4.0.8 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw== + /chainsaw/0.1.0: + dependencies: + traverse: 0.3.9 + dev: false + resolution: + integrity: sha1-XqtQsor+WAdNDVgpE4iCi15fvJg= + /chalk/2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + /chardet/0.7.0: + dev: false + resolution: + integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + /check-error/1.0.2: + dev: false + resolution: + integrity: sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= + /cheerio/1.0.0-rc.3: + dependencies: + css-select: 1.2.0 + dom-serializer: 0.1.1 + entities: 1.1.2 + htmlparser2: 3.10.1 + lodash: 4.17.15 + parse5: 3.0.3 + dev: false + engines: + node: '>= 0.6' + resolution: + integrity: sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA== + /child-process-promise/2.2.1: + dependencies: + cross-spawn: 4.0.2 + node-version: 1.2.0 + promise-polyfill: 6.1.0 + dev: false + resolution: + integrity: sha1-RzChHvYQ+tRQuPIjx50x172tgHQ= + /chokidar/2.1.6: + dependencies: + anymatch: 2.0.0 + async-each: 1.0.3 + braces: 2.3.2 + 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 + dev: false + optionalDependencies: + fsevents: 1.2.9 + resolution: + integrity: sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g== + /chownr/1.1.2: + dev: false + resolution: + integrity: sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A== + /chownr/1.1.3: + dev: false + resolution: + integrity: sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw== + /chrome-trace-event/1.0.2: + dependencies: + tslib: 1.10.0 + dev: false + engines: + node: '>=6.0' + resolution: + integrity: sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== + /cipher-base/1.0.4: + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.0 + dev: false + resolution: + integrity: sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== + /class-utils/0.3.6: + dependencies: + arr-union: 3.1.0 + define-property: 0.2.5 + isobject: 3.0.1 + static-extend: 0.1.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + /classnames/2.2.6: + dev: false + resolution: + integrity: sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== + /cli-cursor/2.1.0: + dependencies: + restore-cursor: 2.0.0 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= + /cli-table/0.3.1: + dependencies: + colors: 1.0.3 + dev: false + engines: + node: '>= 0.2.0' + resolution: + integrity: sha1-9TsFJmqLGguTSz0IIebi3FkUriM= + /cli-width/2.2.0: + dev: false + resolution: + integrity: sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= + /cliui/3.2.0: + dependencies: + string-width: 1.0.2 + strip-ansi: 3.0.1 + wrap-ansi: 2.1.0 + dev: false + resolution: + integrity: sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= + /cliui/5.0.0: + dependencies: + string-width: 3.1.0 + strip-ansi: 5.2.0 + wrap-ansi: 5.1.0 + dev: false + resolution: + integrity: sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== + /clone-buffer/1.0.0: + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-4+JbIHrE5wGvch4staFnksrD3Fg= + /clone-stats/1.0.0: + dev: false + resolution: + integrity: sha1-s3gt/4u1R04Yuba/D9/ngvh3doA= + /clone/2.1.2: + dev: false + engines: + node: '>=0.8' + resolution: + integrity: sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= + /cloneable-readable/1.1.3: + dependencies: + inherits: 2.0.4 + process-nextick-args: 2.0.1 + readable-stream: 2.3.6 + dev: false + resolution: + integrity: sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ== + /code-point-at/1.1.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + /collection-map/1.0.0: + dependencies: + arr-map: 2.0.2 + for-own: 1.0.0 + make-iterator: 1.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw= + /collection-visit/1.0.0: + dependencies: + map-visit: 1.0.0 + object-visit: 1.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + /color-convert/1.9.3: + dependencies: + color-name: 1.1.3 + dev: false + resolution: + integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + /color-name/1.1.3: + dev: false + resolution: + integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + /color-support/1.1.3: + dev: false + hasBin: true + resolution: + integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + /colors/1.0.3: + dev: false + engines: + node: '>=0.1.90' + resolution: + integrity: sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= + /colors/1.2.5: + dev: false + engines: + node: '>=0.1.90' + resolution: + integrity: sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg== + /commander/2.20.0: + dev: false + resolution: + integrity: sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== + /commandpost/1.4.0: + dev: false + resolution: + integrity: sha512-aE2Y4MTFJ870NuB/+2z1cXBhSBBzRydVVjzhFC4gtenEhpnj15yu0qptWGJsO9YGrcPZ3ezX8AWb1VA391MKpQ== + /commondir/1.0.1: + dev: false + resolution: + integrity: sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + /component-emitter/1.3.0: + dev: false + resolution: + integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + /concat-map/0.0.1: + dev: false + resolution: + integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + /concat-stream/1.6.2: + dependencies: + buffer-from: 1.1.1 + inherits: 2.0.4 + readable-stream: 2.3.6 + typedarray: 0.0.6 + dev: false + engines: + '0': node >= 0.8 + resolution: + integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + /console-browserify/1.1.0: + dependencies: + date-now: 0.1.4 + dev: false + resolution: + integrity: sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA= + /constants-browserify/1.0.0: + dev: false + resolution: + integrity: sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= + /convert-source-map/1.6.0: + dependencies: + safe-buffer: 5.1.2 + dev: false + resolution: + integrity: sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A== + /copy-concurrently/1.0.5: + dependencies: + 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 + dev: false + resolution: + integrity: sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A== + /copy-descriptor/0.1.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + /copy-props/2.0.4: + dependencies: + each-props: 1.3.2 + is-plain-object: 2.0.4 + dev: false + resolution: + integrity: sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A== + /core-util-is/1.0.2: + dev: false + resolution: + integrity: sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + /create-ecdh/4.0.3: + dependencies: + bn.js: 4.11.8 + elliptic: 6.5.0 + dev: false + resolution: + integrity: sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw== + /create-hash/1.2.0: + dependencies: + cipher-base: 1.0.4 + inherits: 2.0.4 + md5.js: 1.3.5 + ripemd160: 2.0.2 + sha.js: 2.4.11 + dev: false + resolution: + integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== + /create-hmac/1.1.7: + dependencies: + cipher-base: 1.0.4 + create-hash: 1.2.0 + inherits: 2.0.4 + ripemd160: 2.0.2 + safe-buffer: 5.2.0 + sha.js: 2.4.11 + dev: false + resolution: + integrity: sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== + /cross-spawn/4.0.2: + dependencies: + lru-cache: 4.1.5 + which: 1.3.1 + dev: false + resolution: + integrity: sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE= + /cross-spawn/6.0.5: + dependencies: + nice-try: 1.0.5 + path-key: 2.0.1 + semver: 5.7.0 + shebang-command: 1.2.0 + which: 1.3.1 + dev: false + engines: + node: '>=4.8' + resolution: + integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + /crypto-browserify/3.12.0: + dependencies: + 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 + dev: false + resolution: + integrity: sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== + /css-loader/3.1.0: + dependencies: + camelcase: 5.3.1 + cssesc: 3.0.0 + icss-utils: 4.1.1 + loader-utils: 1.2.3 + normalize-path: 3.0.0 + postcss: 7.0.17 + postcss-modules-extract-imports: 2.0.0 + postcss-modules-local-by-default: 3.0.2 + postcss-modules-scope: 2.1.0 + postcss-modules-values: 3.0.0 + postcss-value-parser: 4.0.1 + schema-utils: 2.1.0 + dev: false + engines: + node: '>= 8.9.0' + peerDependencies: + webpack: ^4.0.0 + resolution: + integrity: sha512-MuL8WsF/KSrHCBCYaozBKlx+r7vIfUaDTEreo7wR7Vv3J6N0z6fqWjRk3e/6wjneitXN1r/Y9FTK1psYNOBdJQ== + /css-loader/3.1.0/webpack@4.39.1: + dependencies: + camelcase: 5.3.1 + cssesc: 3.0.0 + icss-utils: 4.1.1 + loader-utils: 1.2.3 + normalize-path: 3.0.0 + postcss: 7.0.17 + postcss-modules-extract-imports: 2.0.0 + postcss-modules-local-by-default: 3.0.2 + postcss-modules-scope: 2.1.0 + postcss-modules-values: 3.0.0 + postcss-value-parser: 4.0.1 + schema-utils: 2.1.0 + webpack: 4.39.1 + dev: false + engines: + node: '>= 8.9.0' + id: registry.npmjs.org/css-loader/3.1.0 + peerDependencies: + webpack: ^4.0.0 + resolution: + integrity: sha512-MuL8WsF/KSrHCBCYaozBKlx+r7vIfUaDTEreo7wR7Vv3J6N0z6fqWjRk3e/6wjneitXN1r/Y9FTK1psYNOBdJQ== + /css-select/1.2.0: + dependencies: + boolbase: 1.0.0 + css-what: 2.1.3 + domutils: 1.5.1 + nth-check: 1.0.2 + dev: false + resolution: + integrity: sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= + /css-what/2.1.3: + dev: false + resolution: + integrity: sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== + /css/2.2.4: + dependencies: + inherits: 2.0.4 + source-map: 0.6.1 + source-map-resolve: 0.5.2 + urix: 0.1.0 + dev: false + resolution: + integrity: sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw== + /cssesc/3.0.0: + dev: false + engines: + node: '>=4' + hasBin: true + resolution: + integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + /csstype/2.6.6: + dev: false + resolution: + integrity: sha512-RpFbQGUE74iyPgvr46U9t1xoQBM8T4BL8SxrN66Le2xYAPSaDJJKeztV3awugusb3g3G9iL8StmkBBXhcbbXhg== + /cyclist/0.2.2: + dev: false + resolution: + integrity: sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA= + /d/1.0.1: + dependencies: + es5-ext: 0.10.50 + type: 1.0.3 + dev: false + resolution: + integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + /date-now/0.1.4: + dev: false + resolution: + integrity: sha1-6vQ5/U1ISK105cx9vvIAZyueNFs= + /debug-fabulous/1.1.0: + dependencies: + debug: 3.2.6 + memoizee: 0.4.14 + object-assign: 4.1.1 + dev: false + resolution: + integrity: sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg== + /debug/2.6.9: + dependencies: + ms: 2.0.0 + dev: false + resolution: + integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + /debug/3.1.0: + dependencies: + ms: 2.0.0 + dev: false + resolution: + integrity: sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + /debug/3.2.6: + dependencies: + ms: 2.1.2 + dev: false + resolution: + integrity: sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + /debuglog/1.0.1: + dev: false + resolution: + integrity: sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= + /decamelize/1.2.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + /decode-uri-component/0.2.0: + dev: false + engines: + node: '>=0.10' + resolution: + integrity: sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + /deep-eql/3.0.1: + dependencies: + type-detect: 4.0.8 + dev: false + engines: + node: '>=0.12' + resolution: + integrity: sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== + /default-compare/1.0.0: + dependencies: + kind-of: 5.1.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ== + /default-resolution/2.0.0: + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ= + /define-properties/1.1.3: + dependencies: + object-keys: 1.1.1 + dev: false + engines: + node: '>= 0.4' + resolution: + integrity: sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + /define-property/0.2.5: + dependencies: + is-descriptor: 0.1.6 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + /define-property/1.0.0: + dependencies: + is-descriptor: 1.0.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + /define-property/2.0.2: + dependencies: + is-descriptor: 1.0.2 + isobject: 3.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + /denodeify/1.2.1: + dev: false + resolution: + integrity: sha1-OjYof1A05pnnV3kBBSwubJQlFjE= + /des.js/1.0.0: + dependencies: + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + dev: false + resolution: + integrity: sha1-wHTS4qpqipoH29YfmhXCzYPsjsw= + /detect-file/1.0.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= + /detect-newline/2.1.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I= + /dezalgo/1.0.3: + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 + dev: false + resolution: + integrity: sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY= + /didyoumean/1.2.1: + dev: false + resolution: + integrity: sha1-6S7f2tplN9SE1zwBcv0eugxJdv8= + /diff/3.5.0: + dev: false + engines: + node: '>=0.3.1' + resolution: + integrity: sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + /diff/4.0.1: + dev: false + engines: + node: '>=0.3.1' + resolution: + integrity: sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q== + /diffie-hellman/5.0.3: + dependencies: + bn.js: 4.11.8 + miller-rabin: 4.0.1 + randombytes: 2.1.0 + dev: false + resolution: + integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== + /dom-serializer/0.1.1: + dependencies: + domelementtype: 1.3.1 + entities: 1.1.2 + dev: false + resolution: + integrity: sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA== + /dom-serializer/0.2.1: + dependencies: + domelementtype: 2.0.1 + entities: 2.0.0 + dev: false + resolution: + integrity: sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q== + /domain-browser/1.2.0: + dev: false + engines: + node: '>=0.4' + npm: '>=1.2' + resolution: + integrity: sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== + /domelementtype/1.3.1: + dev: false + resolution: + integrity: sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== + /domelementtype/2.0.1: + dev: false + resolution: + integrity: sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== + /domhandler/2.4.2: + dependencies: + domelementtype: 1.3.1 + dev: false + resolution: + integrity: sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== + /domutils/1.5.1: + dependencies: + dom-serializer: 0.2.1 + domelementtype: 1.3.1 + dev: false + resolution: + integrity: sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= + /domutils/1.7.0: + dependencies: + dom-serializer: 0.2.1 + domelementtype: 1.3.1 + dev: false + resolution: + integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== + /duplexer2/0.1.4: + dependencies: + readable-stream: 2.3.6 + dev: false + resolution: + integrity: sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= + /duplexify/3.7.1: + dependencies: + end-of-stream: 1.4.1 + inherits: 2.0.4 + readable-stream: 2.3.6 + stream-shift: 1.0.0 + dev: false + resolution: + integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== + /each-props/1.3.2: + dependencies: + is-plain-object: 2.0.4 + object.defaults: 1.1.0 + dev: false + resolution: + integrity: sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA== + /editorconfig/0.15.3: + dependencies: + commander: 2.20.0 + lru-cache: 4.1.5 + semver: 5.7.0 + sigmund: 1.0.1 + dev: false + hasBin: true + resolution: + integrity: sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g== + /elliptic/6.5.0: + dependencies: + 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 + dev: false + resolution: + integrity: sha512-eFOJTMyCYb7xtE/caJ6JJu+bhi67WCYNbkGSknu20pmM8Ke/bqOfdnZWxyoGN26JgfxTbXrsCkEw4KheCT/KGg== + /emoji-regex/7.0.3: + dev: false + resolution: + integrity: sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + /emojis-list/2.1.0: + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-TapNnbAPmBmIDHn6RXrlsJof04k= + /end-of-stream/1.4.1: + dependencies: + once: 1.4.0 + dev: false + resolution: + integrity: sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== + /enhanced-resolve/4.1.0: + dependencies: + graceful-fs: 4.2.1 + memory-fs: 0.4.1 + tapable: 1.1.3 + dev: false + engines: + node: '>=6.9.0' + resolution: + integrity: sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng== + /entities/1.1.2: + dev: false + resolution: + integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== + /entities/2.0.0: + dev: false + resolution: + integrity: sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== + /errno/0.1.7: + dependencies: + prr: 1.0.1 + dev: false + hasBin: true + resolution: + integrity: sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== + /error-ex/1.3.2: + dependencies: + is-arrayish: 0.2.1 + dev: false + resolution: + integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + /es-abstract/1.13.0: + dependencies: + es-to-primitive: 1.2.0 + function-bind: 1.1.1 + has: 1.0.3 + is-callable: 1.1.4 + is-regex: 1.0.4 + object-keys: 1.1.1 + dev: false + engines: + node: '>= 0.4' + resolution: + integrity: sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== + /es-abstract/1.15.0: + dependencies: + es-to-primitive: 1.2.0 + function-bind: 1.1.1 + has: 1.0.3 + has-symbols: 1.0.0 + is-callable: 1.1.4 + is-regex: 1.0.4 + object-inspect: 1.6.0 + object-keys: 1.1.1 + string.prototype.trimleft: 2.1.0 + string.prototype.trimright: 2.1.0 + dev: false + engines: + node: '>= 0.4' + resolution: + integrity: sha512-bhkEqWJ2t2lMeaJDuk7okMkJWI/yqgH/EoGwpcvv0XW9RWQsRspI4wt6xuyuvMvvQE3gg/D9HXppgk21w78GyQ== + /es-to-primitive/1.2.0: + dependencies: + is-callable: 1.1.4 + is-date-object: 1.0.1 + is-symbol: 1.0.2 + dev: false + engines: + node: '>= 0.4' + resolution: + integrity: sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg== + /es5-ext/0.10.50: + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.1 + next-tick: 1.0.0 + dev: false + resolution: + integrity: sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw== + /es6-iterator/2.0.3: + dependencies: + d: 1.0.1 + es5-ext: 0.10.50 + es6-symbol: 3.1.1 + dev: false + resolution: + integrity: sha1-p96IkUGgWpSwhUQDstCg+/qY87c= + /es6-promise/4.2.8: + dev: false + resolution: + integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + /es6-promisify/5.0.0: + dependencies: + es6-promise: 4.2.8 + dev: false + resolution: + integrity: sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= + /es6-symbol/3.1.1: + dependencies: + d: 1.0.1 + es5-ext: 0.10.50 + dev: false + resolution: + integrity: sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc= + /es6-weak-map/2.0.3: + dependencies: + d: 1.0.1 + es5-ext: 0.10.50 + es6-iterator: 2.0.3 + es6-symbol: 3.1.1 + dev: false + resolution: + integrity: sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== + /escape-string-regexp/1.0.5: + dev: false + engines: + node: '>=0.8.0' + resolution: + integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + /eslint-scope/4.0.3: + dependencies: + esrecurse: 4.2.1 + estraverse: 4.2.0 + dev: false + engines: + node: '>=4.0.0' + resolution: + integrity: sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== + /esprima/4.0.1: + dev: false + engines: + node: '>=4' + hasBin: true + resolution: + integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + /esrecurse/4.2.1: + dependencies: + estraverse: 4.2.0 + dev: false + engines: + node: '>=4.0' + resolution: + integrity: sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== + /estraverse/4.2.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= + /event-emitter/0.3.5: + dependencies: + d: 1.0.1 + es5-ext: 0.10.50 + dev: false + resolution: + integrity: sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk= + /events/3.0.0: + dev: false + engines: + node: '>=0.8.x' + resolution: + integrity: sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA== + /evp_bytestokey/1.0.3: + dependencies: + md5.js: 1.3.5 + safe-buffer: 5.2.0 + dev: false + resolution: + integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== + /execa/1.0.0: + dependencies: + 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 + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + /expand-brackets/2.1.4: + dependencies: + 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 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + /expand-tilde/2.0.2: + dependencies: + homedir-polyfill: 1.0.3 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-l+gBqgUt8CRU3kawK/YhZCzchQI= + /extend-shallow/2.0.1: + dependencies: + is-extendable: 0.1.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + /extend-shallow/3.0.2: + dependencies: + assign-symbols: 1.0.0 + is-extendable: 1.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + /extend/3.0.2: + dev: false + resolution: + integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + /external-editor/3.1.0: + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + /extglob/2.0.4: + dependencies: + 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 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + /fancy-log/1.3.3: + dependencies: + ansi-gray: 0.1.1 + color-support: 1.1.3 + parse-node-version: 1.0.1 + time-stamp: 1.1.0 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw== + /fast-deep-equal/2.0.1: + dev: false + resolution: + integrity: sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= + /fast-json-stable-stringify/2.0.0: + dev: false + resolution: + integrity: sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + /fast-safe-stringify/1.1.13: + dev: false + resolution: + integrity: sha1-oB6c2cnkkXFcmKdaQtXwu9EH/3Y= + /fd-slicer/1.1.0: + dependencies: + pend: 1.2.0 + dev: false + resolution: + integrity: sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= + /figgy-pudding/3.5.1: + dev: false + resolution: + integrity: sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w== + /figures/2.0.0: + dependencies: + escape-string-regexp: 1.0.5 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= + /fill-range/4.0.0: + dependencies: + extend-shallow: 2.0.1 + is-number: 3.0.0 + repeat-string: 1.6.1 + to-regex-range: 2.1.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + /find-cache-dir/2.1.0: + dependencies: + commondir: 1.0.1 + make-dir: 2.1.0 + pkg-dir: 3.0.0 + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== + /find-up/1.1.2: + dependencies: + path-exists: 2.1.0 + pinkie-promise: 2.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= + /find-up/3.0.0: + dependencies: + locate-path: 3.0.0 + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + /findup-sync/2.0.0: + dependencies: + detect-file: 1.0.0 + is-glob: 3.1.0 + micromatch: 3.1.10 + resolve-dir: 1.0.1 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw= + /findup-sync/3.0.0: + dependencies: + detect-file: 1.0.0 + is-glob: 4.0.1 + micromatch: 3.1.10 + resolve-dir: 1.0.1 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== + /fined/1.2.0: + dependencies: + expand-tilde: 2.0.2 + is-plain-object: 2.0.4 + object.defaults: 1.1.0 + object.pick: 1.3.0 + parse-filepath: 1.0.2 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng== + /flagged-respawn/1.0.1: + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q== + /flat/4.1.0: + dependencies: + is-buffer: 2.0.4 + dev: false + hasBin: true + resolution: + integrity: sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw== + /flush-write-stream/1.1.1: + dependencies: + inherits: 2.0.4 + readable-stream: 2.3.6 + dev: false + resolution: + integrity: sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== + /for-in/1.0.2: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + /for-own/1.0.0: + dependencies: + for-in: 1.0.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs= + /fragment-cache/0.2.1: + dependencies: + map-cache: 0.2.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + /from2/2.3.0: + dependencies: + inherits: 2.0.4 + readable-stream: 2.3.6 + dev: false + resolution: + integrity: sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= + /fs-extra/7.0.1: + dependencies: + graceful-fs: 4.2.1 + jsonfile: 4.0.0 + universalify: 0.1.2 + dev: false + engines: + node: '>=6 <7 || >=8' + resolution: + integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + /fs-extra/8.1.0: + dependencies: + graceful-fs: 4.2.1 + jsonfile: 4.0.0 + universalify: 0.1.2 + dev: false + engines: + node: '>=6 <7 || >=8' + resolution: + integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + /fs-minipass/1.2.7: + dependencies: + minipass: 2.9.0 + dev: false + resolution: + integrity: sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== + /fs-mkdirp-stream/1.0.0: + dependencies: + graceful-fs: 4.2.1 + through2: 2.0.5 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes= + /fs-write-stream-atomic/1.0.10: + dependencies: + graceful-fs: 4.2.1 + iferr: 0.1.5 + imurmurhash: 0.1.4 + readable-stream: 2.3.6 + dev: false + resolution: + integrity: sha1-tH31NJPvkR33VzHnCp3tAYnbQMk= + /fs.realpath/1.0.0: + dev: false + resolution: + integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + /fsevents/1.2.9: + bundledDependencies: + - node-pre-gyp + dependencies: + nan: 2.14.0 + dev: false + engines: + node: '>=4.0' + optional: true + requiresBuild: true + resolution: + integrity: sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw== + /fstream/1.0.12: + dependencies: + graceful-fs: 4.2.2 + inherits: 2.0.4 + mkdirp: 0.5.1 + rimraf: 2.7.1 + dev: false + engines: + node: '>=0.6' + resolution: + integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== + /function-bind/1.1.1: + dev: false + resolution: + integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + /get-caller-file/1.0.3: + dev: false + resolution: + integrity: sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== + /get-caller-file/2.0.5: + dev: false + engines: + node: 6.* || 8.* || >= 10.* + resolution: + integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + /get-func-name/2.0.0: + dev: false + resolution: + integrity: sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= + /get-stream/4.1.0: + dependencies: + pump: 3.0.0 + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + /get-value/2.0.6: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + /git-repo-info/2.1.0: + dev: false + engines: + node: '>= 4.0' + resolution: + integrity: sha512-+kigfDB7j3W80f74BoOUX+lKOmf4pR3/i2Ww6baKTCPe2hD4FRdjhV3s4P5Dy0Tak1uY1891QhKoYNtnyX2VvA== + /glob-escape/0.0.2: + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-nCf3gh7RwTd1gvPv2VWOP2dWKO0= + /glob-parent/3.1.0: + dependencies: + is-glob: 3.1.0 + path-dirname: 1.0.2 + dev: false + resolution: + integrity: sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= + /glob-promise/3.4.0: + dependencies: + '@types/glob': 7.1.1 + dev: false + engines: + node: '>=4' + peerDependencies: + glob: '*' + resolution: + integrity: sha512-q08RJ6O+eJn+dVanerAndJwIcumgbDdYiUT7zFQl3Wm1xD6fBKtah7H8ZJChj4wP+8C+QfeVy8xautR7rdmKEw== + /glob-promise/3.4.0/glob@7.1.4: + dependencies: + '@types/glob': 7.1.1 + glob: 7.1.4 + dev: false + engines: + node: '>=4' + id: registry.npmjs.org/glob-promise/3.4.0 + peerDependencies: + glob: '*' + resolution: + integrity: sha512-q08RJ6O+eJn+dVanerAndJwIcumgbDdYiUT7zFQl3Wm1xD6fBKtah7H8ZJChj4wP+8C+QfeVy8xautR7rdmKEw== + /glob-stream/6.1.0: + dependencies: + extend: 3.0.2 + glob: 7.1.4 + glob-parent: 3.1.0 + is-negated-glob: 1.0.0 + ordered-read-streams: 1.0.1 + pumpify: 1.5.1 + readable-stream: 2.3.6 + remove-trailing-separator: 1.1.0 + to-absolute-glob: 2.0.2 + unique-stream: 2.3.1 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ= + /glob-watcher/5.0.3: + dependencies: + anymatch: 2.0.0 + async-done: 1.3.2 + chokidar: 2.1.6 + is-negated-glob: 1.0.0 + just-debounce: 1.0.0 + object.defaults: 1.1.0 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg== + /glob/7.0.6: + dependencies: + 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 + dev: false + resolution: + integrity: sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo= + /glob/7.1.3: + dependencies: + 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 + dev: false + resolution: + integrity: sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + /glob/7.1.4: + dependencies: + 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 + dev: false + resolution: + integrity: sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== + /global-modules/1.0.0: + dependencies: + global-prefix: 1.0.2 + is-windows: 1.0.2 + resolve-dir: 1.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== + /global-modules/2.0.0: + dependencies: + global-prefix: 3.0.0 + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + /global-prefix/1.0.2: + dependencies: + expand-tilde: 2.0.2 + homedir-polyfill: 1.0.3 + ini: 1.3.5 + is-windows: 1.0.2 + which: 1.3.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-2/dDxsFJklk8ZVVoy2btMsASLr4= + /global-prefix/3.0.0: + dependencies: + ini: 1.3.5 + kind-of: 6.0.2 + which: 1.3.1 + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== + /glogg/1.0.2: + dependencies: + sparkles: 1.0.1 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA== + /google-protobuf/3.9.1: + dev: false + resolution: + integrity: sha512-tkz7SVwBktFbqFK3teXFUY/VM57+mbUgV9bSD+sZH1ocHJ7uk7BfEWMRdU24dd0ciUDokreA7ghH2fYFIczQdw== + /graceful-fs/4.2.1: + dev: false + resolution: + integrity: sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw== + /graceful-fs/4.2.2: + dev: false + resolution: + integrity: sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q== + /growl/1.10.5: + dev: false + engines: + node: '>=4.x' + resolution: + integrity: sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== + /gulp-cli/2.2.0: + dependencies: + ansi-colors: 1.1.0 + archy: 1.0.0 + array-sort: 1.0.0 + color-support: 1.1.3 + concat-stream: 1.6.2 + copy-props: 2.0.4 + fancy-log: 1.3.3 + gulplog: 1.0.0 + interpret: 1.2.0 + isobject: 3.0.1 + liftoff: 3.1.0 + matchdep: 2.0.0 + mute-stdout: 1.0.1 + pretty-hrtime: 1.0.3 + replace-homedir: 1.0.0 + semver-greatest-satisfied-range: 1.1.0 + v8flags: 3.1.3 + yargs: 7.1.0 + dev: false + engines: + node: '>= 0.10' + hasBin: true + resolution: + integrity: sha512-rGs3bVYHdyJpLqR0TUBnlcZ1O5O++Zs4bA0ajm+zr3WFCfiSLjGwoCBqFs18wzN+ZxahT9DkOK5nDf26iDsWjA== + /gulp-sourcemaps/2.6.5: + dependencies: + '@gulp-sourcemaps/identity-map': 1.0.2 + '@gulp-sourcemaps/map-sources': 1.0.0 + acorn: 5.7.3 + convert-source-map: 1.6.0 + css: 2.2.4 + debug-fabulous: 1.1.0 + detect-newline: 2.1.0 + graceful-fs: 4.2.1 + source-map: 0.6.1 + strip-bom-string: 1.0.0 + through2: 2.0.5 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha512-SYLBRzPTew8T5Suh2U8jCSDKY+4NARua4aqjj8HOysBh2tSgT9u4jc1FYirAdPx1akUxxDeK++fqw6Jg0LkQRg== + /gulp-typescript/5.0.1: + dependencies: + ansi-colors: 3.2.4 + plugin-error: 1.0.1 + source-map: 0.7.3 + through2: 3.0.1 + vinyl: 2.2.0 + vinyl-fs: 3.0.3 + dev: false + engines: + node: '>= 8' + peerDependencies: + typescript: ~2.7.1 || >=2.8.0-dev || >=2.9.0-dev || ~3.0.0 || >=3.0.0-dev || >=3.1.0-dev || >= 3.2.0-dev || >= 3.3.0-dev + resolution: + integrity: sha512-YuMMlylyJtUSHG1/wuSVTrZp60k1dMEFKYOvDf7OvbAJWrDtxxD4oZon4ancdWwzjj30ztiidhe4VXJniF0pIQ== + /gulp-typescript/5.0.1/typescript@3.5.3: + dependencies: + ansi-colors: 3.2.4 + plugin-error: 1.0.1 + source-map: 0.7.3 + through2: 3.0.1 + typescript: 3.5.3 + vinyl: 2.2.0 + vinyl-fs: 3.0.3 + dev: false + engines: + node: '>= 8' + id: registry.npmjs.org/gulp-typescript/5.0.1 + peerDependencies: + typescript: ~2.7.1 || >=2.8.0-dev || >=2.9.0-dev || ~3.0.0 || >=3.0.0-dev || >=3.1.0-dev || >= 3.2.0-dev || >= 3.3.0-dev + resolution: + integrity: sha512-YuMMlylyJtUSHG1/wuSVTrZp60k1dMEFKYOvDf7OvbAJWrDtxxD4oZon4ancdWwzjj30ztiidhe4VXJniF0pIQ== + /gulp/4.0.2: + dependencies: + glob-watcher: 5.0.3 + gulp-cli: 2.2.0 + undertaker: 1.2.1 + vinyl-fs: 3.0.3 + dev: false + engines: + node: '>= 0.10' + hasBin: true + resolution: + integrity: sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA== + /gulplog/1.0.0: + dependencies: + glogg: 1.0.2 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-4oxNRdBey77YGDY86PnFkmIp/+U= + /has-flag/3.0.0: + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + /has-symbols/1.0.0: + dev: false + engines: + node: '>= 0.4' + resolution: + integrity: sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= + /has-value/0.3.1: + dependencies: + get-value: 2.0.6 + has-values: 0.1.4 + isobject: 2.1.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + /has-value/1.0.0: + dependencies: + get-value: 2.0.6 + has-values: 1.0.0 + isobject: 3.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + /has-values/0.1.4: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-bWHeldkd/Km5oCCJrThL/49it3E= + /has-values/1.0.0: + dependencies: + is-number: 3.0.0 + kind-of: 4.0.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + /has/1.0.3: + dependencies: + function-bind: 1.1.1 + dev: false + engines: + node: '>= 0.4.0' + resolution: + integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + /hash-base/3.0.4: + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.0 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-X8hoaEfs1zSZQDMZprCj8/auSRg= + /hash.js/1.1.7: + dependencies: + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + dev: false + resolution: + integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + /he/1.2.0: + dev: false + hasBin: true + resolution: + integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + /hmac-drbg/1.0.1: + dependencies: + hash.js: 1.1.7 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + dev: false + resolution: + integrity: sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= + /homedir-polyfill/1.0.3: + dependencies: + parse-passwd: 1.0.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== + /hosted-git-info/2.8.2: + dependencies: + lru-cache: 5.1.1 + dev: false + resolution: + integrity: sha512-CyjlXII6LMsPMyUzxpTt8fzh5QwzGqPmQXgY/Jyf4Zfp27t/FvfhwoE/8laaMUcMy816CkWF20I7NeQhwwY88w== + /hosted-git-info/2.8.5: + dev: false + resolution: + integrity: sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg== + /htmlparser2/3.10.1: + dependencies: + domelementtype: 1.3.1 + domhandler: 2.4.2 + domutils: 1.7.0 + entities: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.4.0 + dev: false + resolution: + integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== + /http-proxy-agent/2.1.0: + dependencies: + agent-base: 4.3.0 + debug: 3.1.0 + dev: false + engines: + node: '>= 4.5.0' + resolution: + integrity: sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== + /https-browserify/1.0.0: + dev: false + resolution: + integrity: sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= + /https-proxy-agent/2.2.2: + dependencies: + agent-base: 4.3.0 + debug: 3.2.6 + dev: false + engines: + node: '>= 4.5.0' + resolution: + integrity: sha512-c8Ndjc9Bkpfx/vCJueCPy0jlP4ccCCSNDp8xwCZzPjKJUm+B+u9WX2x98Qx4n1PiMNTWo3D7KK5ifNV/yJyRzg== + /iconv-lite/0.4.24: + dependencies: + safer-buffer: 2.1.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + /icss-utils/4.1.1: + dependencies: + postcss: 7.0.17 + dev: false + engines: + node: '>= 6' + resolution: + integrity: sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA== + /ieee754/1.1.13: + dev: false + resolution: + integrity: sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== + /iferr/0.1.5: + dev: false + resolution: + integrity: sha1-xg7taebY/bazEEofy8ocGS3FtQE= + /ignore-walk/3.0.1: + dependencies: + minimatch: 3.0.4 + dev: false + resolution: + integrity: sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ== + /immediate/3.0.6: + dev: false + resolution: + integrity: sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= + /import-local/2.0.0: + dependencies: + pkg-dir: 3.0.0 + resolve-cwd: 2.0.0 + dev: false + engines: + node: '>=6' + hasBin: true + resolution: + integrity: sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== + /imurmurhash/0.1.4: + dev: false + engines: + node: '>=0.8.19' + resolution: + integrity: sha1-khi5srkoojixPcT7a21XbyMUU+o= + /indexes-of/1.0.1: + dev: false + resolution: + integrity: sha1-8w9xbI4r00bHtn0985FVZqfAVgc= + /individual/3.0.0: + dev: false + resolution: + integrity: sha1-58pPhfiVewGHNPKFdQ3CLsL5hi0= + /infer-owner/1.0.4: + dev: false + resolution: + integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + /inflight/1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: false + resolution: + integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + /inherits/2.0.1: + dev: false + resolution: + integrity: sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= + /inherits/2.0.3: + dev: false + resolution: + integrity: sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + /inherits/2.0.4: + dev: false + resolution: + integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + /ini/1.3.5: + dev: false + resolution: + integrity: sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + /inquirer/6.2.2: + dependencies: + ansi-escapes: 3.2.0 + chalk: 2.4.2 + cli-cursor: 2.1.0 + cli-width: 2.2.0 + external-editor: 3.1.0 + figures: 2.0.0 + lodash: 4.17.15 + mute-stream: 0.0.7 + run-async: 2.3.0 + rxjs: 6.5.3 + string-width: 2.1.1 + strip-ansi: 5.2.0 + through: 2.3.8 + dev: false + engines: + node: '>=6.0.0' + resolution: + integrity: sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA== + /interpret/1.2.0: + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== + /invert-kv/1.0.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-EEqOSqym09jNFXqO+L+rLXo//bY= + /invert-kv/2.0.0: + dev: false + engines: + node: '>=4' + resolution: + integrity: sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== + /is-absolute/1.0.0: + dependencies: + is-relative: 1.0.0 + is-windows: 1.0.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== + /is-accessor-descriptor/0.1.6: + dependencies: + kind-of: 3.2.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + /is-accessor-descriptor/1.0.0: + dependencies: + kind-of: 6.0.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + /is-arrayish/0.2.1: + dev: false + resolution: + integrity: sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + /is-binary-path/1.0.1: + dependencies: + binary-extensions: 1.13.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= + /is-buffer/1.1.6: + dev: false + resolution: + integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + /is-buffer/2.0.4: + dev: false + engines: + node: '>=4' + resolution: + integrity: sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== + /is-callable/1.1.4: + dev: false + engines: + node: '>= 0.4' + resolution: + integrity: sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== + /is-data-descriptor/0.1.4: + dependencies: + kind-of: 3.2.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + /is-data-descriptor/1.0.0: + dependencies: + kind-of: 6.0.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + /is-date-object/1.0.1: + dev: false + engines: + node: '>= 0.4' + resolution: + integrity: sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY= + /is-descriptor/0.1.6: + dependencies: + is-accessor-descriptor: 0.1.6 + is-data-descriptor: 0.1.4 + kind-of: 5.1.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + /is-descriptor/1.0.2: + dependencies: + is-accessor-descriptor: 1.0.0 + is-data-descriptor: 1.0.0 + kind-of: 6.0.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + /is-extendable/0.1.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + /is-extendable/1.0.1: + dependencies: + is-plain-object: 2.0.4 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + /is-extglob/2.1.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + /is-fullwidth-code-point/1.0.0: + dependencies: + number-is-nan: 1.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + /is-fullwidth-code-point/2.0.0: + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + /is-glob/3.1.0: + dependencies: + is-extglob: 2.1.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= + /is-glob/4.0.1: + dependencies: + is-extglob: 2.1.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + /is-negated-glob/1.0.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI= + /is-number/3.0.0: + dependencies: + kind-of: 3.2.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + /is-number/4.0.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== + /is-plain-object/2.0.4: + dependencies: + isobject: 3.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + /is-promise/2.1.0: + dev: false + resolution: + integrity: sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= + /is-regex/1.0.4: + dependencies: + has: 1.0.3 + dev: false + engines: + node: '>= 0.4' + resolution: + integrity: sha1-VRdIm1RwkbCTDglWVM7SXul+lJE= + /is-relative/1.0.0: + dependencies: + is-unc-path: 1.0.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== + /is-stream/1.1.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-EtSj3U5o4Lec6428hBc66A2RykQ= + /is-symbol/1.0.2: + dependencies: + has-symbols: 1.0.0 + dev: false + engines: + node: '>= 0.4' + resolution: + integrity: sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw== + /is-unc-path/1.0.0: + dependencies: + unc-path-regex: 0.1.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== + /is-utf8/0.2.1: + dev: false + resolution: + integrity: sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= + /is-valid-glob/1.0.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao= + /is-windows/1.0.2: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + /is-wsl/1.1.0: + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= + /isarray/1.0.0: + dev: false + resolution: + integrity: sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + /isexe/2.0.0: + dev: false + resolution: + integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + /isobject/2.1.0: + dependencies: + isarray: 1.0.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + /isobject/3.0.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + /jju/1.4.0: + dev: false + resolution: + integrity: sha1-o6vicYryQaKykE+EpiWXDzia4yo= + /js-tokens/4.0.0: + dev: false + resolution: + integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + /js-yaml/3.13.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + dev: false + hasBin: true + resolution: + integrity: sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + /json-parse-better-errors/1.0.2: + dev: false + resolution: + integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + /json-schema-traverse/0.4.1: + dev: false + resolution: + integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + /json-stable-stringify-without-jsonify/1.0.1: + dev: false + resolution: + integrity: sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + /json-stringify-safe/5.0.1: + dev: false + resolution: + integrity: sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + /json5/1.0.1: + dependencies: + minimist: 1.2.0 + dev: false + hasBin: true + resolution: + integrity: sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + /jsonc-parser/2.1.0: + dev: false + resolution: + integrity: sha512-n9GrT8rrr2fhvBbANa1g+xFmgGK5X91KFeDwlKQ3+SJfmH5+tKv/M/kahx/TXOMflfWHKGKqKyfHQaLKTNzJ6w== + /jsonfile/4.0.0: + dev: false + optionalDependencies: + graceful-fs: 4.2.1 + resolution: + integrity: sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + /jsonify/0.0.0: + dev: false + resolution: + integrity: sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= + /jszip/3.2.2: + dependencies: + lie: 3.3.0 + pako: 1.0.10 + readable-stream: 2.3.6 + set-immediate-shim: 1.0.1 + dev: false + resolution: + integrity: sha512-NmKajvAFQpbg3taXQXr/ccS2wcucR1AZ+NtyWp2Nq7HHVsXhcJFR8p0Baf32C2yVvBylFWVeKf+WI2AnvlPhpA== + /just-debounce/1.0.0: + dev: false + resolution: + integrity: sha1-h/zPrv/AtozRnVX2cilD+SnqNeo= + /kind-of/3.2.2: + dependencies: + is-buffer: 1.1.6 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + /kind-of/4.0.0: + dependencies: + is-buffer: 1.1.6 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-IIE989cSkosgc3hpGkUGb65y3Vc= + /kind-of/5.1.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + /kind-of/6.0.2: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== + /last-run/1.1.1: + dependencies: + default-resolution: 2.0.0 + es6-weak-map: 2.0.3 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-RblpQsF7HHnHchmCWbqUO+v4yls= + /lazystream/1.0.0: + dependencies: + readable-stream: 2.3.6 + dev: false + engines: + node: '>= 0.6.3' + resolution: + integrity: sha1-9plf4PggOS9hOWvolGJAe7dxaOQ= + /lcid/1.0.0: + dependencies: + invert-kv: 1.0.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= + /lcid/2.0.0: + dependencies: + invert-kv: 2.0.0 + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA== + /lead/1.0.0: + dependencies: + flush-write-stream: 1.1.1 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI= + /leb/0.3.0: + dev: false + resolution: + integrity: sha1-Mr7p+tFoMo1q6oUi2DP0GA7tHaM= + /lie/3.3.0: + dependencies: + immediate: 3.0.6 + dev: false + resolution: + integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== + /liftoff/3.1.0: + dependencies: + extend: 3.0.2 + findup-sync: 3.0.0 + fined: 1.2.0 + flagged-respawn: 1.0.1 + is-plain-object: 2.0.4 + object.map: 1.0.1 + rechoir: 0.6.2 + resolve: 1.11.1 + dev: false + engines: + node: '>= 0.8' + resolution: + integrity: sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog== + /linkify-it/2.2.0: + dependencies: + uc.micro: 1.0.6 + dev: false + resolution: + integrity: sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw== + /listenercount/1.0.1: + dev: false + resolution: + integrity: sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc= + /load-json-file/1.1.0: + dependencies: + graceful-fs: 4.2.1 + parse-json: 2.2.0 + pify: 2.3.0 + pinkie-promise: 2.0.1 + strip-bom: 2.0.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA= + /load-json-file/4.0.0: + dependencies: + graceful-fs: 4.2.1 + parse-json: 4.0.0 + pify: 3.0.0 + strip-bom: 3.0.0 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-L19Fq5HjMhYjT9U62rZo607AmTs= + /loader-runner/2.4.0: + dev: false + engines: + node: '>=4.3.0 <5.0.0 || >=5.10' + resolution: + integrity: sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== + /loader-utils/1.2.3: + dependencies: + big.js: 5.2.2 + emojis-list: 2.1.0 + json5: 1.0.1 + dev: false + engines: + node: '>=4.0.0' + resolution: + integrity: sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== + /locate-path/3.0.0: + dependencies: + p-locate: 3.0.0 + path-exists: 3.0.0 + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + /lodash.get/4.4.2: + dev: false + resolution: + integrity: sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + /lodash.isequal/4.5.0: + dev: false + resolution: + integrity: sha1-QVxEePK8wwEgwizhDtMib30+GOA= + /lodash/4.17.15: + dev: false + resolution: + integrity: sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + /log-symbols/2.2.0: + dependencies: + chalk: 2.4.2 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== + /loose-envify/1.4.0: + dependencies: + js-tokens: 4.0.0 + dev: false + hasBin: true + resolution: + integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + /lru-cache/4.1.5: + dependencies: + pseudomap: 1.0.2 + yallist: 2.1.2 + dev: false + resolution: + integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== + /lru-cache/5.1.1: + dependencies: + yallist: 3.0.3 + dev: false + resolution: + integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + /lru-queue/0.1.0: + dependencies: + es5-ext: 0.10.50 + dev: false + resolution: + integrity: sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM= + /make-dir/2.1.0: + dependencies: + pify: 4.0.1 + semver: 5.7.0 + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + /make-error/1.3.5: + dev: false + resolution: + integrity: sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g== + /make-iterator/1.0.1: + dependencies: + kind-of: 6.0.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw== + /mamacro/0.0.3: + dev: false + resolution: + integrity: sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA== + /map-age-cleaner/0.1.3: + dependencies: + p-defer: 1.0.0 + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== + /map-cache/0.2.2: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + /map-visit/1.0.0: + dependencies: + object-visit: 1.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + /markdown-it/8.4.2: + dependencies: + argparse: 1.0.10 + entities: 1.1.2 + linkify-it: 2.2.0 + mdurl: 1.0.1 + uc.micro: 1.0.6 + dev: false + hasBin: true + resolution: + integrity: sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ== + /matchdep/2.0.0: + dependencies: + findup-sync: 2.0.0 + micromatch: 3.1.10 + resolve: 1.11.1 + stack-trace: 0.0.10 + dev: false + engines: + node: '>= 0.10.0' + resolution: + integrity: sha1-xvNINKDY28OzfCfui7yyfHd1WC4= + /md5.js/1.3.5: + dependencies: + hash-base: 3.0.4 + inherits: 2.0.4 + safe-buffer: 5.2.0 + dev: false + resolution: + integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== + /mdurl/1.0.1: + dev: false + resolution: + integrity: sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= + /mem/4.3.0: + dependencies: + map-age-cleaner: 0.1.3 + mimic-fn: 2.1.0 + p-is-promise: 2.1.0 + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w== + /memoizee/0.4.14: + dependencies: + d: 1.0.1 + es5-ext: 0.10.50 + es6-weak-map: 2.0.3 + event-emitter: 0.3.5 + is-promise: 2.1.0 + lru-queue: 0.1.0 + next-tick: 1.0.0 + timers-ext: 0.1.7 + dev: false + resolution: + integrity: sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg== + /memory-fs/0.4.1: + dependencies: + errno: 0.1.7 + readable-stream: 2.3.6 + dev: false + resolution: + integrity: sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= + /memorystream/0.3.1: + dev: false + engines: + node: '>= 0.10.0' + resolution: + integrity: sha1-htcJCzDORV1j+64S3aUaR93K+bI= + /micromatch/3.1.10: + dependencies: + 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 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + /miller-rabin/4.0.1: + dependencies: + bn.js: 4.11.8 + brorand: 1.1.0 + dev: false + hasBin: true + resolution: + integrity: sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== + /mime/1.6.0: + dev: false + engines: + node: '>=4' + hasBin: true + resolution: + integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + /mimic-fn/1.2.0: + dev: false + engines: + node: '>=4' + resolution: + integrity: sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== + /mimic-fn/2.1.0: + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + /minimalistic-assert/1.0.1: + dev: false + resolution: + integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + /minimalistic-crypto-utils/1.0.1: + dev: false + resolution: + integrity: sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= + /minimatch/3.0.4: + dependencies: + brace-expansion: 1.1.11 + dev: false + resolution: + integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + /minimist/0.0.8: + dev: false + resolution: + integrity: sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + /minimist/1.2.0: + dev: false + resolution: + integrity: sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + /minipass/2.9.0: + dependencies: + safe-buffer: 5.2.0 + yallist: 3.1.1 + dev: false + resolution: + integrity: sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== + /minizlib/1.3.3: + dependencies: + minipass: 2.9.0 + dev: false + resolution: + integrity: sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== + /mississippi/3.0.0: + dependencies: + 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 + dev: false + engines: + node: '>=4.0.0' + resolution: + integrity: sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA== + /mixin-deep/1.3.2: + dependencies: + for-in: 1.0.2 + is-extendable: 1.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== + /mkdirp-promise/5.0.1: + dependencies: + mkdirp: 0.5.1 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-6bj2jlUsaKnBcTuEiD96HdA5uKE= + /mkdirp/0.5.1: + dependencies: + minimist: 0.0.8 + dev: false + hasBin: true + resolution: + integrity: sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + /mocha/6.2.1: + dependencies: + ansi-colors: 3.2.3 + browser-stdout: 1.3.1 + debug: 3.2.6 + diff: 3.5.0 + escape-string-regexp: 1.0.5 + find-up: 3.0.0 + glob: 7.1.3 + growl: 1.10.5 + he: 1.2.0 + js-yaml: 3.13.1 + log-symbols: 2.2.0 + minimatch: 3.0.4 + mkdirp: 0.5.1 + ms: 2.1.1 + node-environment-flags: 1.0.5 + object.assign: 4.1.0 + strip-json-comments: 2.0.1 + supports-color: 6.0.0 + which: 1.3.1 + wide-align: 1.1.3 + yargs: 13.3.0 + yargs-parser: 13.1.1 + yargs-unparser: 1.6.0 + dev: false + engines: + node: '>= 6.0.0' + hasBin: true + resolution: + integrity: sha512-VCcWkLHwk79NYQc8cxhkmI8IigTIhsCwZ6RTxQsqK6go4UvEhzJkYuHm8B2YtlSxcYq2fY+ucr4JBwoD6ci80A== + /move-concurrently/1.0.1: + dependencies: + 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 + dev: false + resolution: + integrity: sha1-viwAX9oy4LKa8fBdfEszIUxwH5I= + /ms/2.0.0: + dev: false + resolution: + integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + /ms/2.1.1: + dev: false + resolution: + integrity: sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + /ms/2.1.2: + dev: false + resolution: + integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + /mute-stdout/1.0.1: + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg== + /mute-stream/0.0.7: + dev: false + resolution: + integrity: sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= + /mute-stream/0.0.8: + dev: false + resolution: + integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + /mz/2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + dev: false + resolution: + integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + /nan/2.14.0: + dev: false + optional: true + resolution: + integrity: sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + /nanomatch/1.2.13: + dependencies: + 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 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + /ndjson/1.5.0: + dependencies: + json-stringify-safe: 5.0.1 + minimist: 1.2.0 + split2: 2.2.0 + through2: 2.0.5 + dev: false + hasBin: true + resolution: + integrity: sha1-rmA7NrE0vOw0e0UkIrC/mNWDLsg= + /neo-async/2.6.1: + dev: false + resolution: + integrity: sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== + /next-tick/1.0.0: + dev: false + resolution: + integrity: sha1-yobR/ogoFpsBICCOPchCS524NCw= + /nice-try/1.0.5: + dev: false + resolution: + integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + /node-environment-flags/1.0.5: + dependencies: + object.getownpropertydescriptors: 2.0.3 + semver: 5.7.1 + dev: false + resolution: + integrity: sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ== + /node-fetch/2.1.2: + dev: false + engines: + node: 4.x || >=6.0.0 + resolution: + integrity: sha1-q4hOjn5X44qUR1POxwb3iNF2i7U= + /node-fetch/2.6.0: + dev: false + engines: + node: 4.x || >=6.0.0 + resolution: + integrity: sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== + /node-libs-browser/2.2.1: + dependencies: + 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.3.0 + timers-browserify: 2.0.10 + tty-browserify: 0.0.0 + url: 0.11.0 + util: 0.11.1 + vm-browserify: 1.1.0 + dev: false + resolution: + integrity: sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== + /node-version/1.2.0: + dev: false + engines: + node: '>=6.0.0' + resolution: + integrity: sha512-ma6oU4Sk0qOoKEAymVoTvk8EdXEobdS7m/mAGhDJ8Rouugho48crHBORAmy5BoOcv8wraPM6xumapQp5hl4iIQ== + /normalize-package-data/2.5.0: + dependencies: + hosted-git-info: 2.8.2 + resolve: 1.11.1 + semver: 5.7.0 + validate-npm-package-license: 3.0.4 + dev: false + resolution: + integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + /normalize-path/2.1.1: + dependencies: + remove-trailing-separator: 1.1.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + /normalize-path/3.0.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + /now-and-later/2.0.1: + dependencies: + once: 1.4.0 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ== + /npm-bundled/1.0.6: + dev: false + resolution: + integrity: sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== + /npm-package-arg/5.1.2: + dependencies: + hosted-git-info: 2.8.5 + osenv: 0.1.5 + semver: 5.7.1 + validate-npm-package-name: 3.0.0 + dev: false + resolution: + integrity: sha512-wJBsrf0qpypPT7A0LART18hCdyhpCMxeTtcb0X4IZO2jsP6Om7EHN1d9KSKiqD+KVH030RVNpWS9thk+pb7wzA== + /npm-packlist/1.4.4: + dependencies: + ignore-walk: 3.0.1 + npm-bundled: 1.0.6 + dev: false + resolution: + integrity: sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw== + /npm-run-all/4.1.5: + dependencies: + ansi-styles: 3.2.1 + chalk: 2.4.2 + cross-spawn: 6.0.5 + memorystream: 0.3.1 + minimatch: 3.0.4 + pidtree: 0.3.0 + read-pkg: 3.0.0 + shell-quote: 1.6.1 + string.prototype.padend: 3.0.0 + dev: false + engines: + node: '>= 4' + hasBin: true + resolution: + integrity: sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ== + /npm-run-path/2.0.2: + dependencies: + path-key: 2.0.1 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + /nth-check/1.0.2: + dependencies: + boolbase: 1.0.0 + dev: false + resolution: + integrity: sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== + /number-is-nan/1.0.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + /object-assign/4.1.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + /object-copy/0.1.0: + dependencies: + copy-descriptor: 0.1.1 + define-property: 0.2.5 + kind-of: 3.2.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + /object-inspect/1.6.0: + dev: false + resolution: + integrity: sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ== + /object-keys/1.1.1: + dev: false + engines: + node: '>= 0.4' + resolution: + integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + /object-visit/1.0.1: + dependencies: + isobject: 3.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + /object.assign/4.1.0: + dependencies: + define-properties: 1.1.3 + function-bind: 1.1.1 + has-symbols: 1.0.0 + object-keys: 1.1.1 + dev: false + engines: + node: '>= 0.4' + resolution: + integrity: sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + /object.defaults/1.1.0: + dependencies: + array-each: 1.0.1 + array-slice: 1.1.0 + for-own: 1.0.0 + isobject: 3.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8= + /object.getownpropertydescriptors/2.0.3: + dependencies: + define-properties: 1.1.3 + es-abstract: 1.15.0 + dev: false + engines: + node: '>= 0.8' + resolution: + integrity: sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY= + /object.map/1.0.1: + dependencies: + for-own: 1.0.0 + make-iterator: 1.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-z4Plncj8wK1fQlDh94s7gb2AHTc= + /object.pick/1.3.0: + dependencies: + isobject: 3.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + /object.reduce/1.0.1: + dependencies: + for-own: 1.0.0 + make-iterator: 1.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60= + /once/1.4.0: + dependencies: + wrappy: 1.0.2 + dev: false + resolution: + integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + /onetime/2.0.1: + dependencies: + mimic-fn: 1.2.0 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= + /ordered-read-streams/1.0.1: + dependencies: + readable-stream: 2.3.6 + dev: false + resolution: + integrity: sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4= + /os-browserify/0.3.0: + dev: false + resolution: + integrity: sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= + /os-homedir/1.0.2: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-/7xJiDNuDoM94MFox+8VISGqf7M= + /os-locale/1.4.0: + dependencies: + lcid: 1.0.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= + /os-locale/3.1.0: + dependencies: + execa: 1.0.0 + lcid: 2.0.0 + mem: 4.3.0 + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== + /os-tmpdir/1.0.2: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + /os/0.1.1: + dev: false + resolution: + integrity: sha1-IIhF6J4ZOtTZcUdLk5R3NqVtE/M= + /osenv/0.1.5: + dependencies: + os-homedir: 1.0.2 + os-tmpdir: 1.0.2 + dev: false + resolution: + integrity: sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== + /p-defer/1.0.0: + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= + /p-filter/1.0.0: + dependencies: + p-map: 1.2.0 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-Yp0xcVAgnI/VCLoTdxPvS7kg6ds= + /p-finally/1.0.0: + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + /p-is-promise/2.1.0: + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg== + /p-limit/2.2.0: + dependencies: + p-try: 2.2.0 + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ== + /p-locate/3.0.0: + dependencies: + p-limit: 2.2.0 + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + /p-map/1.2.0: + dev: false + engines: + node: '>=4' + resolution: + integrity: sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA== + /p-try/2.2.0: + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + /pako/1.0.10: + dev: false + resolution: + integrity: sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw== + /parallel-transform/1.1.0: + dependencies: + cyclist: 0.2.2 + inherits: 2.0.4 + readable-stream: 2.3.6 + dev: false + resolution: + integrity: sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY= + /parse-asn1/5.1.4: + dependencies: + 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.2.0 + dev: false + resolution: + integrity: sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw== + /parse-filepath/1.0.2: + dependencies: + is-absolute: 1.0.0 + map-cache: 0.2.2 + path-root: 0.1.1 + dev: false + engines: + node: '>=0.8' + resolution: + integrity: sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE= + /parse-json/2.2.0: + dependencies: + error-ex: 1.3.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= + /parse-json/4.0.0: + dependencies: + error-ex: 1.3.2 + json-parse-better-errors: 1.0.2 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + /parse-node-version/1.0.1: + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA== + /parse-passwd/1.0.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= + /parse-semver/1.1.1: + dependencies: + semver: 5.7.1 + dev: false + resolution: + integrity: sha1-mkr9bfBj3Egm+T+6SpnPIj9mbLg= + /parse5/3.0.3: + dependencies: + '@types/node': 12.7.5 + dev: false + resolution: + integrity: sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA== + /pascalcase/0.1.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= + /path-browserify/0.0.1: + dev: false + resolution: + integrity: sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== + /path-dirname/1.0.2: + dev: false + resolution: + integrity: sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= + /path-exists/2.1.0: + dependencies: + pinkie-promise: 2.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= + /path-exists/3.0.0: + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + /path-is-absolute/1.0.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + /path-key/2.0.1: + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + /path-parse/1.0.6: + dev: false + resolution: + integrity: sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + /path-root-regex/0.1.2: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0= + /path-root/0.1.1: + dependencies: + path-root-regex: 0.1.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc= + /path-type/1.1.0: + dependencies: + graceful-fs: 4.2.1 + pify: 2.3.0 + pinkie-promise: 2.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE= + /path-type/3.0.0: + dependencies: + pify: 3.0.0 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== + /pathval/1.1.0: + dev: false + resolution: + integrity: sha1-uULm1L3mUwBe9rcTYd74cn0GReA= + /pbkdf2/3.0.17: + dependencies: + create-hash: 1.2.0 + create-hmac: 1.1.7 + ripemd160: 2.0.2 + safe-buffer: 5.2.0 + sha.js: 2.4.11 + dev: false + engines: + node: '>=0.12' + resolution: + integrity: sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA== + /pend/1.2.0: + dev: false + resolution: + integrity: sha1-elfrVQpng/kRUzH89GY9XI4AelA= + /pidtree/0.3.0: + dev: false + engines: + node: '>=0.10' + hasBin: true + resolution: + integrity: sha512-9CT4NFlDcosssyg8KVFltgokyKZIFjoBxw8CTGy+5F38Y1eQWrt8tRayiUOXE+zVKQnYu5BR8JjCtvK3BcnBhg== + /pify/2.3.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-7RQaasBDqEnqWISY59yosVMw6Qw= + /pify/3.0.0: + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= + /pify/4.0.1: + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + /pinkie-promise/2.0.1: + dependencies: + pinkie: 2.0.4 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-ITXW36ejWMBprJsXh3YogihFD/o= + /pinkie/2.0.4: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-clVrgM+g1IqXToDnckjoDtT3+HA= + /pkg-dir/3.0.0: + dependencies: + find-up: 3.0.0 + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== + /plugin-error/1.0.1: + dependencies: + ansi-colors: 1.1.0 + arr-diff: 4.0.0 + arr-union: 3.1.0 + extend-shallow: 3.0.2 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA== + /posix-character-classes/0.1.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + /postcss-modules-extract-imports/2.0.0: + dependencies: + postcss: 7.0.17 + dev: false + engines: + node: '>= 6' + resolution: + integrity: sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ== + /postcss-modules-local-by-default/3.0.2: + dependencies: + icss-utils: 4.1.1 + postcss: 7.0.17 + postcss-selector-parser: 6.0.2 + postcss-value-parser: 4.0.1 + dev: false + engines: + node: '>= 6' + resolution: + integrity: sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ== + /postcss-modules-scope/2.1.0: + dependencies: + postcss: 7.0.17 + postcss-selector-parser: 6.0.2 + dev: false + engines: + node: '>= 6' + resolution: + integrity: sha512-91Rjps0JnmtUB0cujlc8KIKCsJXWjzuxGeT/+Q2i2HXKZ7nBUeF9YQTZZTNvHVoNYj1AthsjnGLtqDUE0Op79A== + /postcss-modules-values/3.0.0: + dependencies: + icss-utils: 4.1.1 + postcss: 7.0.17 + dev: false + resolution: + integrity: sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg== + /postcss-selector-parser/6.0.2: + dependencies: + cssesc: 3.0.0 + indexes-of: 1.0.1 + uniq: 1.0.1 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== + /postcss-value-parser/4.0.1: + dev: false + resolution: + integrity: sha512-3Jk+/CVH0HBfgSSFWALKm9Hyzf4kumPjZfUxkRYZNcqFztELb2APKxv0nlX8HCdc1/ymePmT/nFf1ST6fjWH2A== + /postcss/7.0.17: + dependencies: + chalk: 2.4.2 + source-map: 0.6.1 + supports-color: 6.1.0 + dev: false + engines: + node: '>=6.0.0' + resolution: + integrity: sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ== + /pretty-hrtime/1.0.3: + dev: false + engines: + node: '>= 0.8' + resolution: + integrity: sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= + /process-nextick-args/2.0.1: + dev: false + resolution: + integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + /process/0.11.10: + dev: false + engines: + node: '>= 0.6.0' + resolution: + integrity: sha1-czIwDoQBYb2j5podHZGn1LwW8YI= + /promise-inflight/1.0.1: + dev: false + resolution: + integrity: sha1-mEcocL8igTL8vdhoEputEsPAKeM= + /promise-polyfill/6.1.0: + dev: false + resolution: + integrity: sha1-36lpQ+qcEh/KTem1hoyznTRy4Fc= + /prop-types/15.7.2: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.8.6 + dev: false + resolution: + integrity: sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + /prr/1.0.1: + dev: false + resolution: + integrity: sha1-0/wRS6BplaRexok/SEzrHXj19HY= + /pseudomap/1.0.2: + dev: false + resolution: + integrity: sha1-8FKijacOYYkX7wqKw0wa5aaChrM= + /public-encrypt/4.0.3: + dependencies: + 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.2.0 + dev: false + resolution: + integrity: sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== + /pump/2.0.1: + dependencies: + end-of-stream: 1.4.1 + once: 1.4.0 + dev: false + resolution: + integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== + /pump/3.0.0: + dependencies: + end-of-stream: 1.4.1 + once: 1.4.0 + dev: false + resolution: + integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + /pumpify/1.5.1: + dependencies: + duplexify: 3.7.1 + inherits: 2.0.4 + pump: 2.0.1 + dev: false + resolution: + integrity: sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== + /punycode/1.3.2: + dev: false + resolution: + integrity: sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= + /punycode/1.4.1: + dev: false + resolution: + integrity: sha1-wNWmOycYgArY4esPpSachN1BhF4= + /punycode/2.1.1: + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + /querystring-es3/0.2.1: + dev: false + engines: + node: '>=0.4.x' + resolution: + integrity: sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= + /querystring/0.2.0: + dev: false + engines: + node: '>=0.4.x' + resolution: + integrity: sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + /ramda/0.25.0: + dev: false + resolution: + integrity: sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ== + /randombytes/2.1.0: + dependencies: + safe-buffer: 5.2.0 + dev: false + resolution: + integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + /randomfill/1.0.4: + dependencies: + randombytes: 2.1.0 + safe-buffer: 5.2.0 + dev: false + resolution: + integrity: sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== + /react-dom/16.8.6: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + prop-types: 15.7.2 + scheduler: 0.13.6 + dev: false + peerDependencies: + react: ^16.0.0 + resolution: + integrity: sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA== + /react-dom/16.8.6/react@16.8.6: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + prop-types: 15.7.2 + react: 16.8.6 + scheduler: 0.13.6 + dev: false + id: registry.npmjs.org/react-dom/16.8.6 + peerDependencies: + react: ^16.0.0 + resolution: + integrity: sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA== + /react-is/16.8.6: + dev: false + resolution: + integrity: sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA== + /react/16.8.6: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + prop-types: 15.7.2 + scheduler: 0.13.6 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw== + /read-package-json/2.1.0: + dependencies: + glob: 7.1.4 + json-parse-better-errors: 1.0.2 + normalize-package-data: 2.5.0 + slash: 1.0.0 + dev: false + optionalDependencies: + graceful-fs: 4.2.2 + resolution: + integrity: sha512-KLhu8M1ZZNkMcrq1+0UJbR8Dii8KZUqB0Sha4mOx/bknfKI/fyrQVrG/YIt2UOtG667sD8+ee4EXMM91W9dC+A== + /read-package-tree/5.1.6: + dependencies: + debuglog: 1.0.1 + dezalgo: 1.0.3 + once: 1.4.0 + read-package-json: 2.1.0 + readdir-scoped-modules: 1.1.0 + dev: false + resolution: + integrity: sha512-FCX1aT3GWyY658wzDICef4p+n0dB+ENRct8E/Qyvppj6xVpOYerBHfUu7OP5Rt1/393Tdglguf5ju5DEX4wZNg== + /read-pkg-up/1.0.1: + dependencies: + find-up: 1.1.2 + read-pkg: 1.1.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI= + /read-pkg/1.1.0: + dependencies: + load-json-file: 1.1.0 + normalize-package-data: 2.5.0 + path-type: 1.1.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-9f+qXs0pyzHAR0vKfXVra7KePyg= + /read-pkg/3.0.0: + dependencies: + load-json-file: 4.0.0 + normalize-package-data: 2.5.0 + path-type: 3.0.0 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= + /read/1.0.7: + dependencies: + mute-stream: 0.0.8 + dev: false + engines: + node: '>=0.8' + resolution: + integrity: sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ= + /readable-stream/2.3.6: + dependencies: + 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 + dev: false + resolution: + integrity: sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== + /readable-stream/3.4.0: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + dev: false + engines: + node: '>= 6' + resolution: + integrity: sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ== + /readdir-scoped-modules/1.1.0: + dependencies: + debuglog: 1.0.1 + dezalgo: 1.0.3 + graceful-fs: 4.2.2 + once: 1.4.0 + dev: false + resolution: + integrity: sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw== + /readdirp/2.2.1: + dependencies: + graceful-fs: 4.2.1 + micromatch: 3.1.10 + readable-stream: 2.3.6 + dev: false + engines: + node: '>=0.10' + resolution: + integrity: sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== + /rechoir/0.6.2: + dependencies: + resolve: 1.11.1 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q= + /reflect-metadata/0.1.13: + dev: false + resolution: + integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== + /regex-not/1.0.2: + dependencies: + extend-shallow: 3.0.2 + safe-regex: 1.1.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + /remove-bom-buffer/3.0.0: + dependencies: + is-buffer: 1.1.6 + is-utf8: 0.2.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ== + /remove-bom-stream/1.2.0: + dependencies: + remove-bom-buffer: 3.0.0 + safe-buffer: 5.2.0 + through2: 2.0.5 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-BfGlk/FuQuH7kOv1nejlaVJflSM= + /remove-trailing-separator/1.1.0: + dev: false + resolution: + integrity: sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + /repeat-element/1.1.3: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + /repeat-string/1.6.1: + dev: false + engines: + node: '>=0.10' + resolution: + integrity: sha1-jcrkcOHIirwtYA//Sndihtp15jc= + /replace-ext/1.0.0: + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= + /replace-homedir/1.0.0: + dependencies: + homedir-polyfill: 1.0.3 + is-absolute: 1.0.0 + remove-trailing-separator: 1.1.0 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw= + /require-directory/2.1.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + /require-main-filename/1.0.1: + dev: false + resolution: + integrity: sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= + /require-main-filename/2.0.0: + dev: false + resolution: + integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + /resolve-cwd/2.0.0: + dependencies: + resolve-from: 3.0.0 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= + /resolve-dir/1.0.1: + dependencies: + expand-tilde: 2.0.2 + global-modules: 1.0.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-eaQGRMNivoLybv/nOcm7U4IEb0M= + /resolve-from/3.0.0: + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-six699nWiBvItuZTM17rywoYh0g= + /resolve-options/1.1.0: + dependencies: + value-or-function: 3.0.0 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-MrueOcBtZzONyTeMDW1gdFZq0TE= + /resolve-url/0.2.1: + dev: false + resolution: + integrity: sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + /resolve/1.11.1: + dependencies: + path-parse: 1.0.6 + dev: false + resolution: + integrity: sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw== + /restore-cursor/2.0.0: + dependencies: + onetime: 2.0.1 + signal-exit: 3.0.2 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-n37ih/gv0ybU/RYpI9YhKe7g368= + /ret/0.1.15: + dev: false + engines: + node: '>=0.12' + resolution: + integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + /rimraf/2.6.3: + dependencies: + glob: 7.1.4 + dev: false + hasBin: true + resolution: + integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + /rimraf/2.7.1: + dependencies: + glob: 7.1.4 + dev: false + hasBin: true + resolution: + integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + /ripemd160/2.0.2: + dependencies: + hash-base: 3.0.4 + inherits: 2.0.4 + dev: false + resolution: + integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + /run-async/2.3.0: + dependencies: + is-promise: 2.1.0 + dev: false + engines: + node: '>=0.12.0' + resolution: + integrity: sha1-A3GrSuC91yDUFm19/aZP96RFpsA= + /run-queue/1.0.3: + dependencies: + aproba: 1.2.0 + dev: false + resolution: + integrity: sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec= + /rxjs/6.5.3: + dependencies: + tslib: 1.10.0 + dev: false + engines: + npm: '>=2.0.0' + resolution: + integrity: sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA== + /safe-buffer/5.1.2: + dev: false + resolution: + integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + /safe-buffer/5.2.0: + dev: false + resolution: + integrity: sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + /safe-regex/1.1.0: + dependencies: + ret: 0.1.15 + dev: false + resolution: + integrity: sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + /safer-buffer/2.1.2: + dev: false + resolution: + integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + /sax/1.2.4: + dev: false + resolution: + integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + /scheduler/0.13.6: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + dev: false + resolution: + integrity: sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ== + /schema-utils/1.0.0: + dependencies: + ajv: 6.10.2 + ajv-errors: /ajv-errors/1.0.1/ajv@6.10.2 + ajv-keywords: /ajv-keywords/3.4.1/ajv@6.10.2 + dev: false + engines: + node: '>= 4' + resolution: + integrity: sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== + /schema-utils/2.1.0: + dependencies: + ajv: 6.10.2 + ajv-keywords: /ajv-keywords/3.4.1/ajv@6.10.2 + dev: false + engines: + node: '>= 8.9.0' + resolution: + integrity: sha512-g6SViEZAfGNrToD82ZPUjq52KUPDYc+fN5+g6Euo5mLokl/9Yx14z0Cu4RR1m55HtBXejO0sBt+qw79axN+Fiw== + /semver-greatest-satisfied-range/1.1.0: + dependencies: + sver-compat: 1.5.0 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-E+jCZYq5aRywzXEJMkAoDTb3els= + /semver/5.3.0: + dev: false + hasBin: true + resolution: + integrity: sha1-myzl094C0XxgEq0yaqa00M9U+U8= + /semver/5.7.0: + dev: false + hasBin: true + resolution: + integrity: sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== + /semver/5.7.1: + dev: false + hasBin: true + resolution: + integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + /serialize-javascript/1.7.0: + dev: false + resolution: + integrity: sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA== + /set-blocking/2.0.0: + dev: false + resolution: + integrity: sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + /set-immediate-shim/1.0.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= + /set-value/2.0.1: + dependencies: + extend-shallow: 2.0.1 + is-extendable: 0.1.1 + is-plain-object: 2.0.4 + split-string: 3.1.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + /setimmediate/1.0.5: + dev: false + resolution: + integrity: sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= + /sha.js/2.4.11: + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.0 + dev: false + hasBin: true + resolution: + integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + /shebang-command/1.2.0: + dependencies: + shebang-regex: 1.0.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + /shebang-regex/1.0.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + /shell-quote/1.6.1: + dependencies: + array-filter: 0.0.1 + array-map: 0.0.0 + array-reduce: 0.0.0 + jsonify: 0.0.0 + dev: false + resolution: + integrity: sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c= + /sigmund/1.0.1: + dev: false + resolution: + integrity: sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA= + /signal-exit/3.0.2: + dev: false + resolution: + integrity: sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + /slash/1.0.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= + /snapdragon-node/2.1.1: + dependencies: + define-property: 1.0.0 + isobject: 3.0.1 + snapdragon-util: 3.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + /snapdragon-util/3.0.1: + dependencies: + kind-of: 3.2.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + /snapdragon/0.8.2: + dependencies: + 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 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + /source-list-map/2.0.1: + dev: false + resolution: + integrity: sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== + /source-map-resolve/0.5.2: + dependencies: + 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 + dev: false + resolution: + integrity: sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== + /source-map-support/0.5.13: + dependencies: + buffer-from: 1.1.1 + source-map: 0.6.1 + dev: false + resolution: + integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + /source-map-url/0.4.0: + dev: false + resolution: + integrity: sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + /source-map/0.5.7: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + /source-map/0.6.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + /source-map/0.7.3: + dev: false + engines: + node: '>= 8' + resolution: + integrity: sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + /sparkles/1.0.1: + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw== + /spdx-correct/3.1.0: + dependencies: + spdx-expression-parse: 3.0.0 + spdx-license-ids: 3.0.5 + dev: false + resolution: + integrity: sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q== + /spdx-exceptions/2.2.0: + dev: false + resolution: + integrity: sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== + /spdx-expression-parse/3.0.0: + dependencies: + spdx-exceptions: 2.2.0 + spdx-license-ids: 3.0.5 + dev: false + resolution: + integrity: sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== + /spdx-license-ids/3.0.5: + dev: false + resolution: + integrity: sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== + /split-string/3.1.0: + dependencies: + extend-shallow: 3.0.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + /split2/2.2.0: + dependencies: + through2: 2.0.5 + dev: false + resolution: + integrity: sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw== + /sprintf-js/1.0.3: + dev: false + resolution: + integrity: sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + /ssri/6.0.1: + dependencies: + figgy-pudding: 3.5.1 + dev: false + resolution: + integrity: sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA== + /stack-trace/0.0.10: + dev: false + resolution: + integrity: sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= + /static-extend/0.1.2: + dependencies: + define-property: 0.2.5 + object-copy: 0.1.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + /stream-browserify/2.0.2: + dependencies: + inherits: 2.0.4 + readable-stream: 2.3.6 + dev: false + resolution: + integrity: sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== + /stream-each/1.2.3: + dependencies: + end-of-stream: 1.4.1 + stream-shift: 1.0.0 + dev: false + resolution: + integrity: sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw== + /stream-exhaust/1.0.2: + dev: false + resolution: + integrity: sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw== + /stream-http/2.8.3: + dependencies: + builtin-status-codes: 3.0.0 + inherits: 2.0.4 + readable-stream: 2.3.6 + to-arraybuffer: 1.0.1 + xtend: 4.0.2 + dev: false + resolution: + integrity: sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== + /stream-shift/1.0.0: + dev: false + resolution: + integrity: sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI= + /strict-uri-encode/2.0.0: + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-ucczDHBChi9rFC3CdLvMWGbONUY= + /string-width/1.0.2: + dependencies: + code-point-at: 1.1.0 + is-fullwidth-code-point: 1.0.0 + strip-ansi: 3.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + /string-width/2.1.1: + dependencies: + is-fullwidth-code-point: 2.0.0 + strip-ansi: 4.0.0 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + /string-width/3.1.0: + dependencies: + emoji-regex: 7.0.3 + is-fullwidth-code-point: 2.0.0 + strip-ansi: 5.2.0 + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + /string.prototype.padend/3.0.0: + dependencies: + define-properties: 1.1.3 + es-abstract: 1.13.0 + function-bind: 1.1.1 + dev: false + engines: + node: '>= 0.4' + resolution: + integrity: sha1-86rvfBcZ8XDF6rHDK/eA2W4h8vA= + /string.prototype.trimleft/2.1.0: + dependencies: + define-properties: 1.1.3 + function-bind: 1.1.1 + dev: false + engines: + node: '>= 0.4' + resolution: + integrity: sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw== + /string.prototype.trimright/2.1.0: + dependencies: + define-properties: 1.1.3 + function-bind: 1.1.1 + dev: false + engines: + node: '>= 0.4' + resolution: + integrity: sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg== + /string_decoder/1.1.1: + dependencies: + safe-buffer: 5.1.2 + dev: false + resolution: + integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + /string_decoder/1.3.0: + dependencies: + safe-buffer: 5.2.0 + dev: false + resolution: + integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + /strip-ansi/3.0.1: + dependencies: + ansi-regex: 2.1.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + /strip-ansi/4.0.0: + dependencies: + ansi-regex: 3.0.0 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-qEeQIusaw2iocTibY1JixQXuNo8= + /strip-ansi/5.2.0: + dependencies: + ansi-regex: 4.1.0 + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + /strip-bom-string/1.0.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI= + /strip-bom/2.0.0: + dependencies: + is-utf8: 0.2.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4= + /strip-bom/3.0.0: + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + /strip-eof/1.0.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= + /strip-json-comments/2.0.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-PFMZQukIwml8DsNEhYwobHygpgo= + /style-loader/0.23.1: + dependencies: + loader-utils: 1.2.3 + schema-utils: 1.0.0 + dev: false + engines: + node: '>= 0.12.0' + resolution: + integrity: sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg== + /supports-color/5.5.0: + dependencies: + has-flag: 3.0.0 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + /supports-color/6.0.0: + dependencies: + has-flag: 3.0.0 + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg== + /supports-color/6.1.0: + dependencies: + has-flag: 3.0.0 + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + /sver-compat/1.5.0: + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.1 + dev: false + resolution: + integrity: sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg= + /tapable/1.1.3: + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== + /tar/4.4.13: + dependencies: + chownr: 1.1.3 + fs-minipass: 1.2.7 + minipass: 2.9.0 + minizlib: 1.3.3 + mkdirp: 0.5.1 + safe-buffer: 5.2.0 + yallist: 3.1.1 + dev: false + engines: + node: '>=4.5' + resolution: + integrity: sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== + /terser-webpack-plugin/1.4.1: + dependencies: + cacache: 12.0.2 + find-cache-dir: 2.1.0 + is-wsl: 1.1.0 + schema-utils: 1.0.0 + serialize-javascript: 1.7.0 + source-map: 0.6.1 + terser: 4.1.3 + webpack-sources: 1.4.3 + worker-farm: 1.7.0 + dev: false + engines: + node: '>= 6.9.0' + peerDependencies: + webpack: ^4.0.0 + resolution: + integrity: sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg== + /terser/4.1.3: + dependencies: + commander: 2.20.0 + source-map: 0.6.1 + source-map-support: 0.5.13 + dev: false + engines: + node: '>=6.0.0' + hasBin: true + resolution: + integrity: sha512-on13d+cnpn5bMouZu+J8tPYQecsdRJCJuxFJ+FVoPBoLJgk5bCBkp+Uen2hWyi0KIUm6eDarnlAlH+KgIx/PuQ== + /thenify-all/1.6.0: + dependencies: + thenify: 3.3.0 + dev: false + engines: + node: '>=0.8' + resolution: + integrity: sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY= + /thenify/3.3.0: + dependencies: + any-promise: 1.3.0 + dev: false + resolution: + integrity: sha1-5p44obq+lpsBCCB5eLn2K4hgSDk= + /through/2.3.8: + dev: false + resolution: + integrity: sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + /through2-filter/3.0.0: + dependencies: + through2: 2.0.5 + xtend: 4.0.2 + dev: false + resolution: + integrity: sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA== + /through2/2.0.5: + dependencies: + readable-stream: 2.3.6 + xtend: 4.0.2 + dev: false + resolution: + integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + /through2/3.0.1: + dependencies: + readable-stream: 3.4.0 + dev: false + resolution: + integrity: sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww== + /time-stamp/1.1.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-dkpaEa9QVhkhsTPztE5hhofg9cM= + /timers-browserify/2.0.10: + dependencies: + setimmediate: 1.0.5 + dev: false + engines: + node: '>=0.6.0' + resolution: + integrity: sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg== + /timers-ext/0.1.7: + dependencies: + es5-ext: 0.10.50 + next-tick: 1.0.0 + dev: false + resolution: + integrity: sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ== + /tmp/0.0.29: + dependencies: + os-tmpdir: 1.0.2 + dev: false + engines: + node: '>=0.4.0' + resolution: + integrity: sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA= + /tmp/0.0.33: + dependencies: + os-tmpdir: 1.0.2 + dev: false + engines: + node: '>=0.6.0' + resolution: + integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + /tmp/0.1.0: + dependencies: + rimraf: 2.6.3 + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw== + /to-absolute-glob/2.0.2: + dependencies: + is-absolute: 1.0.0 + is-negated-glob: 1.0.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-GGX0PZ50sIItufFFt4z/fQ98hJs= + /to-arraybuffer/1.0.1: + dev: false + resolution: + integrity: sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= + /to-object-path/0.3.0: + dependencies: + kind-of: 3.2.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + /to-regex-range/2.1.1: + dependencies: + is-number: 3.0.0 + repeat-string: 1.6.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + /to-regex/3.0.2: + dependencies: + define-property: 2.0.2 + extend-shallow: 3.0.2 + regex-not: 1.0.2 + safe-regex: 1.1.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + /to-through/2.0.0: + dependencies: + through2: 2.0.5 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY= + /traverse/0.3.9: + dev: false + resolution: + integrity: sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= + /true-case-path/2.2.1: + dev: false + resolution: + integrity: sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q== + /ts-loader/5.4.5: + dependencies: + chalk: 2.4.2 + enhanced-resolve: 4.1.0 + loader-utils: 1.2.3 + micromatch: 3.1.10 + semver: 5.7.0 + dev: false + engines: + node: '>=6.11.5' + peerDependencies: + typescript: '*' + resolution: + integrity: sha512-XYsjfnRQCBum9AMRZpk2rTYSVpdZBpZK+kDh0TeT3kxmQNBDVIeUjdPjY5RZry4eIAb8XHc4gYSUiUWPYvzSRw== + /ts-loader/5.4.5/typescript@3.5.3: + dependencies: + chalk: 2.4.2 + enhanced-resolve: 4.1.0 + loader-utils: 1.2.3 + micromatch: 3.1.10 + semver: 5.7.0 + typescript: 3.5.3 + dev: false + engines: + node: '>=6.11.5' + id: registry.npmjs.org/ts-loader/5.4.5 + peerDependencies: + typescript: '*' + resolution: + integrity: sha512-XYsjfnRQCBum9AMRZpk2rTYSVpdZBpZK+kDh0TeT3kxmQNBDVIeUjdPjY5RZry4eIAb8XHc4gYSUiUWPYvzSRw== + /ts-node/8.3.0: + dependencies: + arg: 4.1.1 + diff: 4.0.1 + make-error: 1.3.5 + source-map-support: 0.5.13 + yn: 3.1.1 + dev: false + engines: + node: '>=4.2.0' + hasBin: true + peerDependencies: + typescript: '>=2.0' + resolution: + integrity: sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ== + /ts-node/8.3.0/typescript@3.5.3: + dependencies: + arg: 4.1.1 + diff: 4.0.1 + make-error: 1.3.5 + source-map-support: 0.5.13 + typescript: 3.5.3 + yn: 3.1.1 + dev: false + engines: + node: '>=4.2.0' + hasBin: true + id: registry.npmjs.org/ts-node/8.3.0 + peerDependencies: + typescript: '>=2.0' + resolution: + integrity: sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ== + /ts-protoc-gen/0.9.0: + dev: false + hasBin: true + resolution: + integrity: sha512-cFEUTY9U9o6C4DPPfMHk2ZUdIAKL91hZN1fyx5Stz3g56BDVOC7hk+r5fEMCAGaaIgi2akkT1a2hrxu1wo2Phg== + /tslib/1.10.0: + dev: false + resolution: + integrity: sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + /tty-browserify/0.0.0: + dev: false + resolution: + integrity: sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= + /tunnel/0.0.4: + dev: false + engines: + node: '>=0.6.11 <=0.7.0 || >=0.7.3' + resolution: + integrity: sha1-LTeFoVjBdMmhbcLARuxfxfF0IhM= + /type-detect/4.0.8: + dev: false + engines: + node: '>=4' + resolution: + integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + /type/1.0.3: + dev: false + resolution: + integrity: sha512-51IMtNfVcee8+9GJvj0spSuFcZHe9vSib6Xtgsny1Km9ugyz2mbS08I3rsUIRYgJohFRFU1160sgRodYz378Hg== + /typed-rest-client/1.2.0: + dependencies: + tunnel: 0.0.4 + underscore: 1.8.3 + dev: false + resolution: + integrity: sha512-FrUshzZ1yxH8YwGR29PWWnfksLEILbWJydU7zfIRkyH7kAEzB62uMAl2WY6EyolWpLpVHeJGgQm45/MaruaHpw== + /typedarray/0.0.6: + dev: false + resolution: + integrity: sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + /typescript-formatter/7.2.2: + dependencies: + commandpost: 1.4.0 + editorconfig: 0.15.3 + dev: false + engines: + node: '>= 4.2.0' + hasBin: true + peerDependencies: + typescript: ^2.1.6 || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev + resolution: + integrity: sha512-V7vfI9XArVhriOTYHPzMU2WUnm5IMdu9X/CPxs8mIMGxmTBFpDABlbkBka64PZJ9/xgQeRpK8KzzAG4MPzxBDQ== + /typescript-formatter/7.2.2/typescript@3.5.3: + dependencies: + commandpost: 1.4.0 + editorconfig: 0.15.3 + typescript: 3.5.3 + dev: false + engines: + node: '>= 4.2.0' + hasBin: true + id: registry.npmjs.org/typescript-formatter/7.2.2 + peerDependencies: + typescript: ^2.1.6 || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev + resolution: + integrity: sha512-V7vfI9XArVhriOTYHPzMU2WUnm5IMdu9X/CPxs8mIMGxmTBFpDABlbkBka64PZJ9/xgQeRpK8KzzAG4MPzxBDQ== + /typescript/3.5.3: + dev: false + engines: + node: '>=4.2.0' + hasBin: true + resolution: + integrity: sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== + /uc.micro/1.0.6: + dev: false + resolution: + integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== + /unc-path-regex/0.1.2: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-5z3T17DXxe2G+6xrCufYxqadUPo= + /underscore/1.8.3: + dev: false + resolution: + integrity: sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI= + /undertaker-registry/1.0.1: + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-XkvaMI5KiirlhPm5pDWaSZglzFA= + /undertaker/1.2.1: + dependencies: + arr-flatten: 1.1.0 + arr-map: 2.0.2 + bach: 1.2.0 + collection-map: 1.0.0 + es6-weak-map: 2.0.3 + last-run: 1.1.1 + object.defaults: 1.1.0 + object.reduce: 1.0.1 + undertaker-registry: 1.0.1 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA== + /union-value/1.0.1: + dependencies: + arr-union: 3.1.0 + get-value: 2.0.6 + is-extendable: 0.1.1 + set-value: 2.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + /uniq/1.0.1: + dev: false + resolution: + integrity: sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= + /unique-filename/1.1.1: + dependencies: + unique-slug: 2.0.2 + dev: false + resolution: + integrity: sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== + /unique-slug/2.0.2: + dependencies: + imurmurhash: 0.1.4 + dev: false + resolution: + integrity: sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== + /unique-stream/2.3.1: + dependencies: + json-stable-stringify-without-jsonify: 1.0.1 + through2-filter: 3.0.0 + dev: false + resolution: + integrity: sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A== + /universalify/0.1.2: + dev: false + engines: + node: '>= 4.0.0' + resolution: + integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + /unset-value/1.0.0: + dependencies: + has-value: 0.3.1 + isobject: 3.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + /unzipper/0.10.5: + dependencies: + big-integer: 1.6.47 + binary: 0.3.0 + bluebird: 3.4.7 + buffer-indexof-polyfill: 1.0.1 + duplexer2: 0.1.4 + fstream: 1.0.12 + graceful-fs: 4.2.2 + listenercount: 1.0.1 + readable-stream: 2.3.6 + setimmediate: 1.0.5 + dev: false + resolution: + integrity: sha512-i5ufkXNjWZYxU/0nKKf6LkvW8kn9YzRvfwuPWjXP+JTFce/8bqeR0gEfbiN2IDdJa6ZU6/2IzFRLK0z1v0uptw== + /upath/1.1.2: + dev: false + engines: + node: '>=4' + resolution: + integrity: sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q== + /uri-js/4.2.2: + dependencies: + punycode: 2.1.1 + dev: false + resolution: + integrity: sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + /urix/0.1.0: + dev: false + resolution: + integrity: sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + /url-join/1.1.0: + dev: false + resolution: + integrity: sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg= + /url/0.11.0: + dependencies: + punycode: 1.3.2 + querystring: 0.2.0 + dev: false + resolution: + integrity: sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= + /use/3.1.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + /util-deprecate/1.0.2: + dev: false + resolution: + integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + /util/0.10.3: + dependencies: + inherits: 2.0.1 + dev: false + resolution: + integrity: sha1-evsa/lCAUkZInj23/g7TeTNqwPk= + /util/0.11.1: + dependencies: + inherits: 2.0.3 + dev: false + resolution: + integrity: sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== + /v8-compile-cache/2.0.3: + dev: false + resolution: + integrity: sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w== + /v8flags/3.1.3: + dependencies: + homedir-polyfill: 1.0.3 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w== + /validate-npm-package-license/3.0.4: + dependencies: + spdx-correct: 3.1.0 + spdx-expression-parse: 3.0.0 + dev: false + resolution: + integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + /validate-npm-package-name/3.0.0: + dependencies: + builtins: 1.0.3 + dev: false + resolution: + integrity: sha1-X6kS2B630MdK/BQN5zF/DKffQ34= + /validator/8.2.0: + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha512-Yw5wW34fSv5spzTXNkokD6S6/Oq92d8q/t14TqsS3fAiA1RYnxSFSIZ+CY3n6PGGRCq5HhJTSepQvFUS2QUDxA== + /value-or-function/3.0.0: + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM= + /vinyl-fs/3.0.3: + dependencies: + fs-mkdirp-stream: 1.0.0 + glob-stream: 6.1.0 + graceful-fs: 4.2.1 + is-valid-glob: 1.0.0 + lazystream: 1.0.0 + lead: 1.0.0 + object.assign: 4.1.0 + pumpify: 1.5.1 + readable-stream: 2.3.6 + remove-bom-buffer: 3.0.0 + remove-bom-stream: 1.2.0 + resolve-options: 1.1.0 + through2: 2.0.5 + to-through: 2.0.0 + value-or-function: 3.0.0 + vinyl: 2.2.0 + vinyl-sourcemap: 1.1.0 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng== + /vinyl-sourcemap/1.1.0: + dependencies: + append-buffer: 1.0.2 + convert-source-map: 1.6.0 + graceful-fs: 4.2.1 + normalize-path: 2.1.1 + now-and-later: 2.0.1 + remove-bom-buffer: 3.0.0 + vinyl: 2.2.0 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY= + /vinyl/2.2.0: + dependencies: + clone: 2.1.2 + clone-buffer: 1.0.0 + clone-stats: 1.0.0 + cloneable-readable: 1.1.3 + remove-trailing-separator: 1.1.0 + replace-ext: 1.0.0 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg== + /vm-browserify/1.1.0: + dev: false + resolution: + integrity: sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw== + /vsce/1.66.0: + dependencies: + azure-devops-node-api: 7.2.0 + chalk: 2.4.2 + cheerio: 1.0.0-rc.3 + commander: 2.20.0 + denodeify: 1.2.1 + didyoumean: 1.2.1 + glob: 7.1.4 + lodash: 4.17.15 + markdown-it: 8.4.2 + mime: 1.6.0 + minimatch: 3.0.4 + osenv: 0.1.5 + parse-semver: 1.1.1 + read: 1.0.7 + semver: 5.7.1 + tmp: 0.0.29 + typed-rest-client: 1.2.0 + url-join: 1.1.0 + yauzl: 2.10.0 + yazl: 2.5.1 + dev: false + engines: + node: '>= 8' + hasBin: true + resolution: + integrity: sha512-Zf4+WD4PhEcOr7jkU08SI9lwFqDhmhk73YOCGQ/tNLaBy+PnnX4eSdqj9LdzDLuI2dsyomJLXzDSNgxuaInxCQ== + /vscode-jsonrpc/4.0.0: + dev: false + engines: + node: '>=8.0.0 || >=10.0.0' + resolution: + integrity: sha512-perEnXQdQOJMTDFNv+UF3h1Y0z4iSiaN9jIlb0OqIYgosPCZGYh/MCUlkFtV2668PL69lRDO32hmvL2yiidUYg== + /vscode-languageclient/5.2.1: + dependencies: + semver: 5.7.0 + vscode-languageserver-protocol: 3.14.1 + dev: false + engines: + vscode: ^1.30 + resolution: + integrity: sha512-7jrS/9WnV0ruqPamN1nE7qCxn0phkH5LjSgSp9h6qoJGoeAKzwKz/PF6M+iGA/aklx4GLZg1prddhEPQtuXI1Q== + /vscode-languageserver-protocol/3.14.1: + dependencies: + vscode-jsonrpc: 4.0.0 + vscode-languageserver-types: 3.14.0 + dev: false + resolution: + integrity: sha512-IL66BLb2g20uIKog5Y2dQ0IiigW0XKrvmWiOvc0yXw80z3tMEzEnHjaGAb3ENuU7MnQqgnYJ1Cl2l9RvNgDi4g== + /vscode-languageserver-types/3.14.0: + dev: false + resolution: + integrity: sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A== + /vscode-test/1.2.0: + dependencies: + http-proxy-agent: 2.1.0 + https-proxy-agent: 2.2.2 + rimraf: 2.7.1 + dev: false + engines: + node: '>=8.9.3' + resolution: + integrity: sha512-aowqgc8gZe0eflzVUXsBjBrlsJ8eC35kfgfSEeHu9PKA1vQKm/3rVK43TlbxGue8hKtZBElNAJ5QuYklR/vLJA== + /watchpack/1.6.0: + dependencies: + chokidar: 2.1.6 + graceful-fs: 4.2.1 + neo-async: 2.6.1 + dev: false + resolution: + integrity: sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA== + /webpack-cli/3.3.6: + dependencies: + 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 + dev: false + engines: + node: '>=6.11.5' + hasBin: true + peerDependencies: + webpack: 4.x.x + resolution: + integrity: sha512-0vEa83M7kJtxK/jUhlpZ27WHIOndz5mghWL2O53kiDoA9DIxSKnfqB92LoqEn77cT4f3H2cZm1BMEat/6AZz3A== + /webpack-cli/3.3.6/webpack@4.39.1: + dependencies: + 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 + webpack: 4.39.1 + yargs: 13.2.4 + dev: false + engines: + node: '>=6.11.5' + hasBin: true + id: registry.npmjs.org/webpack-cli/3.3.6 + peerDependencies: + webpack: 4.x.x + resolution: + integrity: sha512-0vEa83M7kJtxK/jUhlpZ27WHIOndz5mghWL2O53kiDoA9DIxSKnfqB92LoqEn77cT4f3H2cZm1BMEat/6AZz3A== + /webpack-sources/1.4.3: + dependencies: + source-list-map: 2.0.1 + source-map: 0.6.1 + dev: false + resolution: + integrity: sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== + /webpack/4.39.1: + dependencies: + '@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.2.1 + ajv: 6.10.2 + ajv-keywords: /ajv-keywords/3.4.1/ajv@6.10.2 + 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.4.1 + watchpack: 1.6.0 + webpack-sources: 1.4.3 + dev: false + engines: + node: '>=6.11.5' + hasBin: true + resolution: + integrity: sha512-/LAb2TJ2z+eVwisldp3dqTEoNhzp/TLCZlmZm3GGGAlnfIWDgOEE758j/9atklNLfRyhKbZTCOIoPqLJXeBLbQ== + /which-module/1.0.0: + dev: false + resolution: + integrity: sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8= + /which-module/2.0.0: + dev: false + resolution: + integrity: sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + /which/1.3.1: + dependencies: + isexe: 2.0.0 + dev: false + hasBin: true + resolution: + integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + /wide-align/1.1.3: + dependencies: + string-width: 2.1.1 + dev: false + resolution: + integrity: sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + /wordwrap/1.0.0: + dev: false + resolution: + integrity: sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= + /worker-farm/1.7.0: + dependencies: + errno: 0.1.7 + dev: false + resolution: + integrity: sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== + /wrap-ansi/2.1.0: + dependencies: + string-width: 1.0.2 + strip-ansi: 3.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= + /wrap-ansi/5.1.0: + dependencies: + ansi-styles: 3.2.1 + string-width: 3.1.0 + strip-ansi: 5.2.0 + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + /wrappy/1.0.2: + dev: false + resolution: + integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + /xml2js/0.4.19: + dependencies: + sax: 1.2.4 + xmlbuilder: 9.0.7 + dev: false + resolution: + integrity: sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== + /xmlbuilder/9.0.7: + dev: false + engines: + node: '>=4.0' + resolution: + integrity: sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= + /xtend/4.0.2: + dev: false + engines: + node: '>=0.4' + resolution: + integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + /y18n/3.2.1: + dev: false + resolution: + integrity: sha1-bRX7qITAhnnA136I53WegR4H+kE= + /y18n/4.0.0: + dev: false + resolution: + integrity: sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + /yallist/2.1.2: + dev: false + resolution: + integrity: sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= + /yallist/3.0.3: + dev: false + resolution: + integrity: sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== + /yallist/3.1.1: + dev: false + resolution: + integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + /yargs-parser/13.1.1: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + dev: false + resolution: + integrity: sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ== + /yargs-parser/5.0.0: + dependencies: + camelcase: 3.0.0 + dev: false + resolution: + integrity: sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo= + /yargs-unparser/1.6.0: + dependencies: + flat: 4.1.0 + lodash: 4.17.15 + yargs: 13.3.0 + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw== + /yargs/13.2.4: + dependencies: + 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 + dev: false + resolution: + integrity: sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg== + /yargs/13.3.0: + dependencies: + cliui: 5.0.0 + find-up: 3.0.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 3.1.0 + which-module: 2.0.0 + y18n: 4.0.0 + yargs-parser: 13.1.1 + dev: false + resolution: + integrity: sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA== + /yargs/7.1.0: + dependencies: + camelcase: 3.0.0 + cliui: 3.2.0 + decamelize: 1.2.0 + get-caller-file: 1.0.3 + os-locale: 1.4.0 + read-pkg-up: 1.0.1 + require-directory: 2.1.1 + require-main-filename: 1.0.1 + set-blocking: 2.0.0 + string-width: 1.0.2 + which-module: 1.0.0 + y18n: 3.2.1 + yargs-parser: 5.0.0 + dev: false + resolution: + integrity: sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg= + /yauzl/2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + dev: false + resolution: + integrity: sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= + /yazl/2.5.1: + dependencies: + buffer-crc32: 0.2.13 + dev: false + resolution: + integrity: sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw== + /yn/3.1.1: + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + /z-schema/3.18.4: + dependencies: + lodash.get: 4.4.2 + lodash.isequal: 4.5.0 + validator: 8.2.0 + dev: false + hasBin: true + optionalDependencies: + commander: 2.20.0 + resolution: + integrity: sha512-DUOKC/IhbkdLKKiV89gw9DUauTV8U/8yJl1sjf6MtDmzevLKOF2duNJ495S3MFVjqZarr+qNGCPbkg4mu4PpLw== + 'file:projects/build-tasks.tgz': + dependencies: + '@microsoft/node-core-library': 3.13.0 + '@microsoft/rush-lib': 5.11.4 + '@types/child-process-promise': 2.2.1 + '@types/fs-extra': 8.0.0 + '@types/gulp': 4.0.6 + '@types/js-yaml': 3.12.1 + '@types/node': 12.7.0 + '@types/npm-packlist': 1.1.1 + '@types/through2': 2.0.34 + '@types/vinyl': 2.0.3 + ansi-colors: 4.1.1 + child-process-promise: 2.2.1 + fs-extra: 8.1.0 + glob: 7.1.4 + glob-promise: /glob-promise/3.4.0/glob@7.1.4 + gulp: 4.0.2 + gulp-sourcemaps: 2.6.5 + gulp-typescript: /gulp-typescript/5.0.1/typescript@3.5.3 + js-yaml: 3.13.1 + jsonc-parser: 2.1.0 + npm-packlist: 1.4.4 + plugin-error: 1.0.1 + resolve: 1.11.1 + through2: 3.0.1 + typescript: 3.5.3 + typescript-formatter: /typescript-formatter/7.2.2/typescript@3.5.3 + vinyl: 2.2.0 + dev: false + name: '@rush-temp/build-tasks' + resolution: + integrity: sha512-BUyDoHgX1TYQw+gqSHLp/jt669EpGoAxdYk7xJsUQ4LKBumy0+nMRtj/nUhluUP4o8PTAOh8JXhOEPYHmvWpNA== + tarball: 'file:projects/build-tasks.tgz' + version: 0.0.0 + 'file:projects/semmle-bqrs.tgz': + dependencies: + '@types/node': 12.7.12 + leb: 0.3.0 + reflect-metadata: 0.1.13 + typescript-formatter: 7.2.2 + dev: false + name: '@rush-temp/semmle-bqrs' + resolution: + integrity: sha512-sQXalSHsqeFO2hKsaWCj2rZYQmMc28YMnNmG1VTcvc/v8trkJQSqCZs1J7rgZpG+Uj05s5iBbgAa1C0uAXtgbw== + tarball: 'file:projects/semmle-bqrs.tgz' + version: 0.0.0 + 'file:projects/semmle-io-node.tgz': + dependencies: + '@types/fs-extra': 8.0.0 + '@types/node': 12.7.12 + fs-extra: 8.1.0 + typescript-formatter: 7.2.2 + dev: false + name: '@rush-temp/semmle-io-node' + resolution: + integrity: sha512-MD9edC5HjrCfPmhktw6XmWotUmperj27/hDZiuMbuSlJ4jRKyiBtJ8Vk2Y4U41TrzsBlJfAwZW8tetPw5ujiLg== + tarball: 'file:projects/semmle-io-node.tgz' + version: 0.0.0 + 'file:projects/semmle-io.tgz': + dependencies: + '@types/node': 12.7.12 + leb: 0.3.0 + typescript-formatter: 7.2.2 + dev: false + name: '@rush-temp/semmle-io' + resolution: + integrity: sha512-ta1lLi1COIeFwpwH523cWheWx6OE8GTqguQmOA7G6CwRF41RYbbREf/4KlOLKO/uG2akhhl+3gcWY2c5/VDC/A== + tarball: 'file:projects/semmle-io.tgz' + version: 0.0.0 + 'file:projects/semmle-vscode-utils.tgz': + dependencies: + '@types/node': 12.7.0 + '@types/vscode': 1.39.0 + typescript: 3.5.3 + typescript-formatter: /typescript-formatter/7.2.2/typescript@3.5.3 + dev: false + name: '@rush-temp/semmle-vscode-utils' + resolution: + integrity: sha512-Q4k2As+HBO0XM+/LuwUHc8BNAXoDNadmrxy3nlVmvv5UTq8oTsRR2l58GzFxcjS2IDTW1x2o+GYA+PfwXsC34Q== + tarball: 'file:projects/semmle-vscode-utils.tgz' + version: 0.0.0 + 'file:projects/typescript-config.tgz': + dev: false + name: '@rush-temp/typescript-config' + resolution: + integrity: sha512-qJbtY2jvt6LKkmUt/seiYyXSEB6Oip/rW+SxofQEnpyplgIQv7whTZb6g5pwlSLGl8goTaQFm4NfazKhFmxXvQ== + tarball: 'file:projects/typescript-config.tgz' + version: 0.0.0 + 'file:projects/vscode-codeql.tgz': + dependencies: + '@types/chai': 4.1.7 + '@types/classnames': 2.2.9 + '@types/fs-extra': 8.0.0 + '@types/glob': 7.1.1 + '@types/google-protobuf': 3.7.1 + '@types/gulp': 4.0.6 + '@types/jszip': 3.1.6 + '@types/mocha': 5.2.7 + '@types/node': 12.7.0 + '@types/node-fetch': 2.5.2 + '@types/react': 16.8.25 + '@types/react-dom': 16.8.5 + '@types/sarif': 2.1.2 + '@types/tmp': 0.1.0 + '@types/unzipper': 0.10.0 + '@types/vscode': 1.39.0 + '@types/webpack': 4.32.1 + '@types/xml2js': 0.4.4 + chai: 4.2.0 + child-process-promise: 2.2.1 + classnames: 2.2.6 + css-loader: /css-loader/3.1.0/webpack@4.39.1 + fs-extra: 8.1.0 + glob: 7.1.4 + glob-promise: /glob-promise/3.4.0/glob@7.1.4 + google-protobuf: 3.9.1 + gulp: 4.0.2 + gulp-sourcemaps: 2.6.5 + gulp-typescript: /gulp-typescript/5.0.1/typescript@3.5.3 + js-yaml: 3.13.1 + jszip: 3.2.2 + leb: 0.3.0 + mocha: 6.2.1 + node-fetch: 2.6.0 + npm-run-all: 4.1.5 + react: 16.8.6 + react-dom: /react-dom/16.8.6/react@16.8.6 + style-loader: 0.23.1 + through2: 3.0.1 + tmp: 0.1.0 + ts-loader: /ts-loader/5.4.5/typescript@3.5.3 + ts-node: /ts-node/8.3.0/typescript@3.5.3 + ts-protoc-gen: 0.9.0 + typescript: 3.5.3 + typescript-formatter: /typescript-formatter/7.2.2/typescript@3.5.3 + unzipper: 0.10.5 + vinyl: 2.2.0 + vsce: 1.66.0 + vscode-jsonrpc: 4.0.0 + vscode-languageclient: 5.2.1 + vscode-test: 1.2.0 + webpack: 4.39.1 + webpack-cli: /webpack-cli/3.3.6/webpack@4.39.1 + xml2js: 0.4.19 + dev: false + name: '@rush-temp/vscode-codeql' + resolution: + integrity: sha512-3CqFyt5JivXlc2/dJaWxAsgt+IQAJ0GaYvCD9JNyobV2gvB3jlODLgYKtnO3OL56QgBDNVdi5nHhtNst1BSZJQ== + tarball: 'file:projects/vscode-codeql.tgz' + version: 0.0.0 +registry: 'https://registry.npmjs.org/' +shrinkwrapMinorVersion: 9 +shrinkwrapVersion: 3 +specifiers: + '@microsoft/node-core-library': ~3.13.0 + '@microsoft/rush-lib': ~5.11.2 + '@rush-temp/build-tasks': 'file:./projects/build-tasks.tgz' + '@rush-temp/semmle-bqrs': 'file:./projects/semmle-bqrs.tgz' + '@rush-temp/semmle-io': 'file:./projects/semmle-io.tgz' + '@rush-temp/semmle-io-node': 'file:./projects/semmle-io-node.tgz' + '@rush-temp/semmle-vscode-utils': 'file:./projects/semmle-vscode-utils.tgz' + '@rush-temp/typescript-config': 'file:./projects/typescript-config.tgz' + '@rush-temp/vscode-codeql': 'file:./projects/vscode-codeql.tgz' + '@types/chai': ^4.1.7 + '@types/child-process-promise': ^2.2.1 + '@types/classnames': ~2.2.9 + '@types/fs-extra': ^8.0.0 + '@types/glob': ^7.1.1 + '@types/google-protobuf': ^3.2.7 + '@types/gulp': ^4.0.6 + '@types/js-yaml': ~3.12.1 + '@types/jszip': ~3.1.6 + '@types/mocha': ~5.2.7 + '@types/node': ^12.0.8 + '@types/node-fetch': ~2.5.2 + '@types/npm-packlist': ~1.1.1 + '@types/react': ^16.8.17 + '@types/react-dom': ^16.8.4 + '@types/sarif': ~2.1.2 + '@types/through2': ~2.0.34 + '@types/tmp': ^0.1.0 + '@types/unzipper': ~0.10.0 + '@types/vinyl': ~2.0.3 + '@types/vscode': ^1.39.0 + '@types/webpack': ^4.32.1 + '@types/xml2js': ~0.4.4 + ansi-colors: ^4.0.1 + chai: ^4.2.0 + child-process-promise: ^2.2.1 + classnames: ~2.2.6 + css-loader: ~3.1.0 + fs-extra: ^8.1.0 + glob: ^7.1.4 + glob-promise: ^3.4.0 + google-protobuf: ^3.7.1 + gulp: ^4.0.2 + gulp-sourcemaps: ^2.6.5 + gulp-typescript: ^5.0.1 + js-yaml: ^3.12.0 + jsonc-parser: ~2.1.0 + leb: ^0.3.0 + mocha: ~6.2.1 + node-fetch: ~2.6.0 + npm-packlist: ~1.4.4 + npm-run-all: ^4.1.5 + plugin-error: ^1.0.1 + react: ^16.8.6 + react-dom: ^16.8.6 + reflect-metadata: ~0.1.13 + resolve: ~1.11.1 + style-loader: ~0.23.1 + through2: ^3.0.1 + tmp: ^0.1.0 + ts-loader: ^5.4.5 + ts-node: ^8.3.0 + ts-protoc-gen: ^0.9.0 + typescript: ^3.5.2 + typescript-formatter: ^7.2.2 + unzipper: ~0.10.5 + vinyl: ^2.2.0 + vsce: ^1.65.0 + vscode-jsonrpc: ^4.0.0 + vscode-languageclient: ^5.2.1 + vscode-test: ^1.0.0 + webpack: ^4.38.0 + webpack-cli: ^3.3.2 + xml2js: ~0.4.19 diff --git a/common/config/rush/version-policies.json b/common/config/rush/version-policies.json new file mode 100644 index 000000000..317032d62 --- /dev/null +++ b/common/config/rush/version-policies.json @@ -0,0 +1,6 @@ +/** + * This is configuration file is used for advanced publishing configurations with Rush. + * For full documentation, please see https://rushjs.io/pages/configs/version_policies_json/ + */ + +[] diff --git a/common/scripts/install-run-rush.js b/common/scripts/install-run-rush.js new file mode 100644 index 000000000..a877aefe9 --- /dev/null +++ b/common/scripts/install-run-rush.js @@ -0,0 +1,52 @@ +"use strict"; +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See the @microsoft/rush package's LICENSE file for license information. +Object.defineProperty(exports, "__esModule", { value: true }); +// THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED. +// +// This script is intended for usage in an automated build environment where the Rush command may not have +// been preinstalled, or may have an unpredictable version. This script will automatically install the version of Rush +// specified in the rush.json configuration file (if not already installed), and then pass a command-line to it. +// An example usage would be: +// +// node common/scripts/install-run-rush.js install +// +// For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/ +const path = require("path"); +const fs = require("fs"); +const install_run_1 = require("./install-run"); +const PACKAGE_NAME = '@microsoft/rush'; +function getRushVersion() { + const rushJsonFolder = install_run_1.findRushJsonFolder(); + const rushJsonPath = path.join(rushJsonFolder, install_run_1.RUSH_JSON_FILENAME); + try { + const rushJsonContents = fs.readFileSync(rushJsonPath, 'utf-8'); + // Use a regular expression to parse out the rushVersion value because rush.json supports comments, + // but JSON.parse does not and we don't want to pull in more dependencies than we need to in this script. + const rushJsonMatches = rushJsonContents.match(/\"rushVersion\"\s*\:\s*\"([0-9a-zA-Z.+\-]+)\"/); + return rushJsonMatches[1]; + } + catch (e) { + throw new Error(`Unable to determine the required version of Rush from rush.json (${rushJsonFolder}). ` + + 'The \'rushVersion\' field is either not assigned in rush.json or was specified ' + + 'using an unexpected syntax.'); + } +} +function run() { + const [nodePath, /* Ex: /bin/node */ scriptPath, /* /repo/common/scripts/install-run-rush.js */ ...packageBinArgs /* [build, --to, myproject] */] = process.argv; + if (!nodePath || !scriptPath) { + throw new Error('Unexpected exception: could not detect node path or script path'); + } + if (process.argv.length < 3) { + console.log('Usage: install-run-rush.js [args...]'); + console.log('Example: install-run-rush.js build --to myproject'); + process.exit(1); + } + install_run_1.runWithErrorAndStatusCode(() => { + const version = getRushVersion(); + console.log(`The rush.json configuration requests Rush version ${version}`); + return install_run_1.installAndRun(PACKAGE_NAME, version, 'rush', packageBinArgs); + }); +} +run(); +//# sourceMappingURL=install-run-rush.js.map \ No newline at end of file diff --git a/common/scripts/install-run.js b/common/scripts/install-run.js new file mode 100644 index 000000000..ca6ece73f --- /dev/null +++ b/common/scripts/install-run.js @@ -0,0 +1,399 @@ +"use strict"; +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See the @microsoft/rush package's LICENSE file for license information. +Object.defineProperty(exports, "__esModule", { value: true }); +// THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED. +// +// This script is intended for usage in an automated build environment where a Node tool may not have +// been preinstalled, or may have an unpredictable version. This script will automatically install the specified +// version of the specified tool (if not already installed), and then pass a command-line to it. +// An example usage would be: +// +// node common/scripts/install-run.js qrcode@1.2.2 qrcode https://rushjs.io +// +// For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/ +const childProcess = require("child_process"); +const fs = require("fs"); +const os = require("os"); +const path = require("path"); +exports.RUSH_JSON_FILENAME = 'rush.json'; +const INSTALLED_FLAG_FILENAME = 'installed.flag'; +const NODE_MODULES_FOLDER_NAME = 'node_modules'; +const PACKAGE_JSON_FILENAME = 'package.json'; +/** + * Parse a package specifier (in the form of name\@version) into name and version parts. + */ +function parsePackageSpecifier(rawPackageSpecifier) { + rawPackageSpecifier = (rawPackageSpecifier || '').trim(); + const separatorIndex = rawPackageSpecifier.lastIndexOf('@'); + let name; + let version = undefined; + if (separatorIndex === 0) { + // The specifier starts with a scope and doesn't have a version specified + name = rawPackageSpecifier; + } + else if (separatorIndex === -1) { + // The specifier doesn't have a version + name = rawPackageSpecifier; + } + else { + name = rawPackageSpecifier.substring(0, separatorIndex); + version = rawPackageSpecifier.substring(separatorIndex + 1); + } + if (!name) { + throw new Error(`Invalid package specifier: ${rawPackageSpecifier}`); + } + return { name, version }; +} +/** + * Resolve a package specifier to a static version + */ +function resolvePackageVersion(rushCommonFolder, { name, version }) { + if (!version) { + version = '*'; // If no version is specified, use the latest version + } + if (version.match(/^[a-zA-Z0-9\-\+\.]+$/)) { + // If the version contains only characters that we recognize to be used in static version specifiers, + // pass the version through + return version; + } + else { + // version resolves to + try { + const rushTempFolder = ensureAndJoinPath(rushCommonFolder, 'temp'); + const sourceNpmrcFolder = path.join(rushCommonFolder, 'config', 'rush'); + syncNpmrc(sourceNpmrcFolder, rushTempFolder); + const npmPath = getNpmPath(); + // This returns something that looks like: + // @microsoft/rush@3.0.0 '3.0.0' + // @microsoft/rush@3.0.1 '3.0.1' + // ... + // @microsoft/rush@3.0.20 '3.0.20' + // + const npmVersionSpawnResult = childProcess.spawnSync(npmPath, ['view', `${name}@${version}`, 'version', '--no-update-notifier'], { + cwd: rushTempFolder, + stdio: [] + }); + if (npmVersionSpawnResult.status !== 0) { + throw new Error(`"npm view" returned error code ${npmVersionSpawnResult.status}`); + } + const npmViewVersionOutput = npmVersionSpawnResult.stdout.toString(); + const versionLines = npmViewVersionOutput.split('\n').filter((line) => !!line); + const latestVersion = versionLines[versionLines.length - 1]; + if (!latestVersion) { + throw new Error('No versions found for the specified version range.'); + } + const versionMatches = latestVersion.match(/^.+\s\'(.+)\'$/); + if (!versionMatches) { + throw new Error(`Invalid npm output ${latestVersion}`); + } + return versionMatches[1]; + } + catch (e) { + throw new Error(`Unable to resolve version ${version} of package ${name}: ${e}`); + } + } +} +let _npmPath = undefined; +/** + * Get the absolute path to the npm executable + */ +function getNpmPath() { + if (!_npmPath) { + try { + if (os.platform() === 'win32') { + // We're on Windows + const whereOutput = childProcess.execSync('where npm', { stdio: [] }).toString(); + const lines = whereOutput.split(os.EOL).filter((line) => !!line); + // take the last result, we are looking for a .cmd command + // see https://github.com/Microsoft/web-build-tools/issues/759 + _npmPath = lines[lines.length - 1]; + } + else { + // We aren't on Windows - assume we're on *NIX or Darwin + _npmPath = childProcess.execSync('which npm', { stdio: [] }).toString(); + } + } + catch (e) { + throw new Error(`Unable to determine the path to the NPM tool: ${e}`); + } + _npmPath = _npmPath.trim(); + if (!fs.existsSync(_npmPath)) { + throw new Error('The NPM executable does not exist'); + } + } + return _npmPath; +} +exports.getNpmPath = getNpmPath; +let _rushJsonFolder; +/** + * Find the absolute path to the folder containing rush.json + */ +function findRushJsonFolder() { + if (!_rushJsonFolder) { + let basePath = __dirname; + let tempPath = __dirname; + do { + const testRushJsonPath = path.join(basePath, exports.RUSH_JSON_FILENAME); + if (fs.existsSync(testRushJsonPath)) { + _rushJsonFolder = basePath; + break; + } + else { + basePath = tempPath; + } + } while (basePath !== (tempPath = path.dirname(basePath))); // Exit the loop when we hit the disk root + if (!_rushJsonFolder) { + throw new Error('Unable to find rush.json.'); + } + } + return _rushJsonFolder; +} +exports.findRushJsonFolder = findRushJsonFolder; +/** + * Create missing directories under the specified base directory, and return the resolved directory. + * + * Does not support "." or ".." path segments. + * Assumes the baseFolder exists. + */ +function ensureAndJoinPath(baseFolder, ...pathSegments) { + let joinedPath = baseFolder; + try { + for (let pathSegment of pathSegments) { + pathSegment = pathSegment.replace(/[\\\/]/g, '+'); + joinedPath = path.join(joinedPath, pathSegment); + if (!fs.existsSync(joinedPath)) { + fs.mkdirSync(joinedPath); + } + } + } + catch (e) { + throw new Error(`Error building local installation folder (${path.join(baseFolder, ...pathSegments)}): ${e}`); + } + return joinedPath; +} +/** + * As a workaround, _syncNpmrc() copies the .npmrc file to the target folder, and also trims + * unusable lines from the .npmrc file. If the source .npmrc file not exist, then _syncNpmrc() + * will delete an .npmrc that is found in the target folder. + * + * Why are we trimming the .npmrc lines? NPM allows environment variables to be specified in + * the .npmrc file to provide different authentication tokens for different registry. + * However, if the environment variable is undefined, it expands to an empty string, which + * produces a valid-looking mapping with an invalid URL that causes an error. Instead, + * we'd prefer to skip that line and continue looking in other places such as the user's + * home directory. + * + * IMPORTANT: THIS CODE SHOULD BE KEPT UP TO DATE WITH Utilities._syncNpmrc() + */ +function syncNpmrc(sourceNpmrcFolder, targetNpmrcFolder) { + const sourceNpmrcPath = path.join(sourceNpmrcFolder, '.npmrc'); + const targetNpmrcPath = path.join(targetNpmrcFolder, '.npmrc'); + try { + if (fs.existsSync(sourceNpmrcPath)) { + let npmrcFileLines = fs.readFileSync(sourceNpmrcPath).toString().split('\n'); + npmrcFileLines = npmrcFileLines.map((line) => (line || '').trim()); + const resultLines = []; + // Trim out lines that reference environment variables that aren't defined + for (const line of npmrcFileLines) { + // This finds environment variable tokens that look like "${VAR_NAME}" + const regex = /\$\{([^\}]+)\}/g; + const environmentVariables = line.match(regex); + let lineShouldBeTrimmed = false; + if (environmentVariables) { + for (const token of environmentVariables) { + // Remove the leading "${" and the trailing "}" from the token + const environmentVariableName = token.substring(2, token.length - 1); + if (!process.env[environmentVariableName]) { + lineShouldBeTrimmed = true; + break; + } + } + } + if (lineShouldBeTrimmed) { + // Example output: + // "; MISSING ENVIRONMENT VARIABLE: //my-registry.com/npm/:_authToken=${MY_AUTH_TOKEN}" + resultLines.push('; MISSING ENVIRONMENT VARIABLE: ' + line); + } + else { + resultLines.push(line); + } + } + fs.writeFileSync(targetNpmrcPath, resultLines.join(os.EOL)); + } + else if (fs.existsSync(targetNpmrcPath)) { + // If the source .npmrc doesn't exist and there is one in the target, delete the one in the target + fs.unlinkSync(targetNpmrcPath); + } + } + catch (e) { + throw new Error(`Error syncing .npmrc file: ${e}`); + } +} +/** + * Detects if the package in the specified directory is installed + */ +function isPackageAlreadyInstalled(packageInstallFolder) { + try { + const flagFilePath = path.join(packageInstallFolder, INSTALLED_FLAG_FILENAME); + if (!fs.existsSync(flagFilePath)) { + return false; + } + const fileContents = fs.readFileSync(flagFilePath).toString(); + return fileContents.trim() === process.version; + } + catch (e) { + return false; + } +} +/** + * Removes the following files and directories under the specified folder path: + * - installed.flag + * - + * - node_modules + */ +function cleanInstallFolder(rushCommonFolder, packageInstallFolder) { + try { + const flagFile = path.resolve(packageInstallFolder, INSTALLED_FLAG_FILENAME); + if (fs.existsSync(flagFile)) { + fs.unlinkSync(flagFile); + } + const packageLockFile = path.resolve(packageInstallFolder, 'package-lock.json'); + if (fs.existsSync(packageLockFile)) { + fs.unlinkSync(packageLockFile); + } + const nodeModulesFolder = path.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME); + if (fs.existsSync(nodeModulesFolder)) { + const rushRecyclerFolder = ensureAndJoinPath(rushCommonFolder, 'temp', 'rush-recycler', `install-run-${Date.now().toString()}`); + fs.renameSync(nodeModulesFolder, rushRecyclerFolder); + } + } + catch (e) { + throw new Error(`Error cleaning the package install folder (${packageInstallFolder}): ${e}`); + } +} +function createPackageJson(packageInstallFolder, name, version) { + try { + const packageJsonContents = { + 'name': 'ci-rush', + 'version': '0.0.0', + 'dependencies': { + [name]: version + }, + 'description': 'DON\'T WARN', + 'repository': 'DON\'T WARN', + 'license': 'MIT' + }; + const packageJsonPath = path.join(packageInstallFolder, PACKAGE_JSON_FILENAME); + fs.writeFileSync(packageJsonPath, JSON.stringify(packageJsonContents, undefined, 2)); + } + catch (e) { + throw new Error(`Unable to create package.json: ${e}`); + } +} +/** + * Run "npm install" in the package install folder. + */ +function installPackage(packageInstallFolder, name, version) { + try { + console.log(`Installing ${name}...`); + const npmPath = getNpmPath(); + const result = childProcess.spawnSync(npmPath, ['install'], { + stdio: 'inherit', + cwd: packageInstallFolder, + env: process.env + }); + if (result.status !== 0) { + throw new Error('"npm install" encountered an error'); + } + console.log(`Successfully installed ${name}@${version}`); + } + catch (e) { + throw new Error(`Unable to install package: ${e}`); + } +} +/** + * Get the ".bin" path for the package. + */ +function getBinPath(packageInstallFolder, binName) { + const binFolderPath = path.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME, '.bin'); + const resolvedBinName = (os.platform() === 'win32') ? `${binName}.cmd` : binName; + return path.resolve(binFolderPath, resolvedBinName); +} +/** + * Write a flag file to the package's install directory, signifying that the install was successful. + */ +function writeFlagFile(packageInstallFolder) { + try { + const flagFilePath = path.join(packageInstallFolder, INSTALLED_FLAG_FILENAME); + fs.writeFileSync(flagFilePath, process.version); + } + catch (e) { + throw new Error(`Unable to create installed.flag file in ${packageInstallFolder}`); + } +} +function installAndRun(packageName, packageVersion, packageBinName, packageBinArgs) { + const rushJsonFolder = findRushJsonFolder(); + const rushCommonFolder = path.join(rushJsonFolder, 'common'); + const packageInstallFolder = ensureAndJoinPath(rushCommonFolder, 'temp', 'install-run', `${packageName}@${packageVersion}`); + if (!isPackageAlreadyInstalled(packageInstallFolder)) { + // The package isn't already installed + cleanInstallFolder(rushCommonFolder, packageInstallFolder); + const sourceNpmrcFolder = path.join(rushCommonFolder, 'config', 'rush'); + syncNpmrc(sourceNpmrcFolder, packageInstallFolder); + createPackageJson(packageInstallFolder, packageName, packageVersion); + installPackage(packageInstallFolder, packageName, packageVersion); + writeFlagFile(packageInstallFolder); + } + const statusMessage = `Invoking "${packageBinName} ${packageBinArgs.join(' ')}"`; + const statusMessageLine = new Array(statusMessage.length + 1).join('-'); + console.log(os.EOL + statusMessage + os.EOL + statusMessageLine + os.EOL); + const binPath = getBinPath(packageInstallFolder, packageBinName); + const result = childProcess.spawnSync(binPath, packageBinArgs, { + stdio: 'inherit', + cwd: process.cwd(), + env: process.env + }); + return result.status; +} +exports.installAndRun = installAndRun; +function runWithErrorAndStatusCode(fn) { + process.exitCode = 1; + try { + const exitCode = fn(); + process.exitCode = exitCode; + } + catch (e) { + console.error(os.EOL + os.EOL + e.toString() + os.EOL + os.EOL); + } +} +exports.runWithErrorAndStatusCode = runWithErrorAndStatusCode; +function run() { + const [nodePath, /* Ex: /bin/node */ scriptPath, /* /repo/common/scripts/install-run-rush.js */ rawPackageSpecifier, /* qrcode@^1.2.0 */ packageBinName, /* qrcode */ ...packageBinArgs /* [-f, myproject/lib] */] = process.argv; + if (!nodePath) { + throw new Error('Unexpected exception: could not detect node path'); + } + if (path.basename(scriptPath).toLowerCase() !== 'install-run.js') { + // If install-run.js wasn't directly invoked, don't execute the rest of this function. Return control + // to the script that (presumably) imported this file + return; + } + if (process.argv.length < 4) { + console.log('Usage: install-run.js @ [args...]'); + console.log('Example: install-run.js qrcode@1.2.2 qrcode https://rushjs.io'); + process.exit(1); + } + runWithErrorAndStatusCode(() => { + const rushJsonFolder = findRushJsonFolder(); + const rushCommonFolder = ensureAndJoinPath(rushJsonFolder, 'common'); + const packageSpecifier = parsePackageSpecifier(rawPackageSpecifier); + const name = packageSpecifier.name; + const version = resolvePackageVersion(rushCommonFolder, packageSpecifier); + if (packageSpecifier.version !== version) { + console.log(`Resolved to ${name}@${version}`); + } + return installAndRun(name, version, packageBinName, packageBinArgs); + }); +} +run(); +//# sourceMappingURL=install-run.js.map \ No newline at end of file diff --git a/configs/typescript-config/common.tsconfig.json b/configs/typescript-config/common.tsconfig.json new file mode 100644 index 000000000..c846afeaa --- /dev/null +++ b/configs/typescript-config/common.tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "declaration": true, + "module": "commonjs", + "target": "es2017", + "outDir": "out", + "lib": [ + "es6" + ], + "moduleResolution": "node", + "sourceMap": true, + "rootDir": "../../src", + "strictNullChecks": true, + "noFallthroughCasesInSwitch": true, + "preserveWatchOutput": true, + "newLine": "lf", + "noImplicitReturns": true, + "experimentalDecorators": true + }, + "include": [ + "../../src/**/*.ts" + ], + "exclude": [ + "../../node_modules", + "../../test", + "../../**/view" + ] +} \ No newline at end of file diff --git a/configs/typescript-config/extension.tsconfig.json b/configs/typescript-config/extension.tsconfig.json new file mode 100644 index 000000000..11d7e4e5a --- /dev/null +++ b/configs/typescript-config/extension.tsconfig.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": "./common.tsconfig.json", + "compilerOptions": { + "declaration": false, + "strict": true + } +} \ No newline at end of file diff --git a/configs/typescript-config/lib.tsconfig.json b/configs/typescript-config/lib.tsconfig.json new file mode 100644 index 000000000..6b2bdde95 --- /dev/null +++ b/configs/typescript-config/lib.tsconfig.json @@ -0,0 +1,4 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": "./common.tsconfig.json" +} \ No newline at end of file diff --git a/configs/typescript-config/package.json b/configs/typescript-config/package.json new file mode 100644 index 000000000..bb055a5da --- /dev/null +++ b/configs/typescript-config/package.json @@ -0,0 +1,18 @@ +{ + "name": "typescript-config", + "description": "TypeScript configurations", + "author": "GitHub", + "private": true, + "version": "0.0.1", + "publisher": "GitHub", + "repository": { + "type": "git", + "url": "https://github.com/github/vscode-codeql" + }, + "scripts": { + "build": "", + "format": "" + }, + "devDependencies": {}, + "dependencies": {} +} \ No newline at end of file diff --git a/extensions/ql-vscode/.vscodeignore b/extensions/ql-vscode/.vscodeignore new file mode 100644 index 000000000..d81d08e76 --- /dev/null +++ b/extensions/ql-vscode/.vscodeignore @@ -0,0 +1,16 @@ +.vs/** +.vscode/** +.vscode-test/** +typings/** +out/test/** +out/vscode-tests/** +**/@types/** +**/*.ts +test/** +src/** +**/*.map +.gitignore +gulpfile.js/** +tsconfig.json +tsfmt.json +vsc-extension-quickstart.md diff --git a/extensions/ql-vscode/README.md b/extensions/ql-vscode/README.md new file mode 100644 index 000000000..3625cc855 --- /dev/null +++ b/extensions/ql-vscode/README.md @@ -0,0 +1,98 @@ +# CodeQL extension for Visual Studio Code + +This project is an extension for Visual Studio Code that adds rich language support for [CodeQL](https://help.semmle.com/codeql) and allows you to easily find problems in codebases. In particular, the extension: + +* Enables you to use CodeQL to query databases generated from source code. +* Shows the flow of data through the results of path queries, which is essential for triaging security results. +* Provides an easy way to run queries from the large, open source repository of [CodeQL security queries](https://github.com/Semmle/ql). +* Adds IntelliSense to support you writing and editing your own CodeQL query and library files. + +## Quick start overview + +The information in this `README` file describes the quickest way to start using CodeQL. +For information about other configurations, see the separate [CodeQL help](https://help.semmle.com/codeql/codeql-for-vscode.html). + +**Quick start: Installing and configuring the extension** + +1. [Install the extension](#installing-the-extension). +1. [Check access to the CodeQL CLI](#checking-access-to-the-codeql-cli). +1. [Clone the CodeQL starter workspace](#cloning-the-codeql-starter-workspace). + +**Quick start: Using CodeQL** + +1. [Import a database from LGTM.com](#importing-a-database-from-lgtmcom). +1. [Run a query](#running-a-query). + +----- + +## Quick start: Installing and configuring the extension + +### Installing the extension + +The CodeQL extension requires a minimum of Visual Studio Code 1.39. Older versions are not supported. + +1. Install and open Visual Studio Code. +1. Open the Extensions view (press **Ctrl+Shift+X** or **Cmd+Shift+X**). +1. At the top of the Extensions view, type `CodeQL` in the box labeled **Search Extensions in Marketplace**. +1. Locate the CodeQL extension and select **Install**. This will install the extension from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=github.vscode-codeql). + +### Checking access to the CodeQL CLI + +The extension uses the [CodeQL CLI](https://help.semmle.com/codeql/codeql-cli.html) to compile and run queries. The extension automatically manages access to the CLI for you by default (recommended). To check for updates to the CodeQL CLI, you can use the **CodeQL: Check for CLI Updates** command. + +If you want to override the default behavior and use a CodeQL CLI that's already on your machine, see [Configuring access to the CodeQL CLI](https://help.semmle.com/codeql/codeql-for-vscode/procedures/setting-up.html#configuring-access-to-the-codeql-cli). + +If you have any difficulty with CodeQL CLI access, see the **CodeQL Extension Log** in the **Output** view for any error messages. + +### Cloning the CodeQL starter workspace + +When you're working with CodeQL, you need access to the standard CodeQL libraries and queries. +Initially, we recommend that you clone and use the ready-to-use starter workspace, https://github.com/github/vscode-codeql-starter/. +This includes libraries and queries for the main supported languages, with folders set up ready for your custom queries. After cloning the workspace (use `git clone --recursive`), you can use it in the same way as any other VS Code workspace—with the added advantage that you can easily update the CodeQL libraries. + +For information about configuring an existing workspace for CodeQL, [see the documentation](https://help.semmle.com/codeql/codeql-for-vscode/procedures/setting-up.html#updating-an-existing-workspace-for-codeql). + +## Quick start: Using CodeQL + +You can find all the commands contributed by the extension in the Command Palette (**Ctrl+Shift+P** or **Cmd+Shift+P**) by typing `CodeQL`, many of them are also accessible through the interface, and via keyboard shortcuts. + +### Importing a database from LGTM.com + +While you can use the [CodeQL CLI to create your own databases](https://help.semmle.com/codeql/codeql-cli/procedures/create-codeql-database.html), the simplest way to start is by downloading a database from LGTM.com. + +1. Log in to LGTM.com. +1. Find a project you're interested in and display the **Integrations** tab (for example, [Apache Kafka](https://lgtm.com/projects/g/apache/kafka/ci/)). +1. Scroll to the **CodeQL databases for local analysis** section at the bottom of the page. +1. Download databases for the languages that you want to explore. +1. Unzip the databases. +1. For each database that you want to import: + 1. In the VS Code sidebar, go to **CodeQL** > **Databases** and click **+**. + 1. Browse to the unzipped database folder (the parent folder that contains `db-` and `src`) and select **Choose database** to add it. + +When the import is complete, each CodeQL database is displayed in the CodeQL sidebar under **Databases**. + +### Running a query + +The instructions below assume that you're using the CodeQL starter workspace, or that you've added the CodeQL libraries and queries repository to your workspace. + +1. Expand the `ql` folder and locate a query to run. The standard queries are grouped by target language and then type, for example: `ql/java/ql/src/Likely Bugs`. +1. Open a query (`.ql`) file. +3. Right-click in the query window and select **CodeQL: Run Query**. Alternatively, open the Command Palette (**Ctrl+Shift+P** or **Cmd+Shift+P**), type `Run Query`, then select **CodeQL: Run Query**. + +The CodeQL extension runs the query on the current database using the CLI and reports progress in the bottom right corner of the application. +When the results are ready, they're displayed in the CodeQL Query Results view. Use the dropdown menu to choose between different forms of result output. + +If there are any problems running a query, a notification is displayed in the bottom right corner of the application. In addition to the error message, the notification includes details of how to fix the problem. + +## What next? + +For more information about the CodeQL extension, [see the documentation](https://help.semmle.com/codeql/codeql-for-vscode.html). Otherwise, you could: + +* [Create a database for a different codebase](https://help.semmle.com/codeql/codeql-cli/procedures/create-codeql-database.html). +* [Try out variant analysis](https://help.semmle.com/QL/learn-ql/ql-training.html). +* [Learn more about CodeQL](https://help.semmle.com/QL/learn-ql/). +* [Read how security researchers use CodeQL to find CVEs](https://securitylab.github.com/research). + +## License + +The CodeQL extension for Visual Studio Code is [licensed](LICENSE.md) under the MIT License. The version of CodeQL used by the CodeQL extension is subject to the [GitHub CodeQL Terms & Conditions](https://securitylab.github.com/tools/codeql/license). diff --git a/extensions/ql-vscode/gulpfile.js/index.js b/extensions/ql-vscode/gulpfile.js/index.js new file mode 100644 index 000000000..e2c5c8c2c --- /dev/null +++ b/extensions/ql-vscode/gulpfile.js/index.js @@ -0,0 +1,19 @@ +'use strict'; + +require('ts-node').register({}); +const gulp = require('gulp'); +const { + compileTypeScript, + watchTypeScript, + packageExtension, + compileTextMateGrammar, + copyTestData, + copyViewCss +} = require('build-tasks'); +const { compileView } = require('./webpack'); + +exports.buildWithoutPackage = gulp.parallel(compileTypeScript, compileTextMateGrammar, compileView, copyTestData, copyViewCss); +exports.compileTextMateGrammar = compileTextMateGrammar; +exports.default = gulp.series(exports.buildWithoutPackage, packageExtension); +exports.watchTypeScript = watchTypeScript; +exports.compileTypeScript = compileTypeScript; diff --git a/extensions/ql-vscode/gulpfile.js/webpack.config.ts b/extensions/ql-vscode/gulpfile.js/webpack.config.ts new file mode 100644 index 000000000..b94a433cb --- /dev/null +++ b/extensions/ql-vscode/gulpfile.js/webpack.config.ts @@ -0,0 +1,61 @@ +import * as path from 'path'; +import * as webpack from 'webpack'; + +export const config: webpack.Configuration = { + mode: 'development', + entry: { + resultsView: './src/view/results.tsx' + }, + output: { + path: path.resolve(__dirname, '..', 'out'), + filename: "[name].js" + }, + devtool: 'source-map', + resolve: { + extensions: ['.js', '.ts', '.tsx', '.json'] + }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + loader: 'ts-loader', + }, + { + test: /\.less$/, + use: [ + { + loader: 'style-loader' + }, + { + loader: 'css-loader', + options: { + importLoaders: 1, + sourceMap: true + } + }, + { + loader: 'less-loader', + options: { + javascriptEnabled: true, + sourceMap: true + } + } + ] + }, + { + test: /\.css$/, + use: [ + { + loader: 'style-loader' + }, + { + loader: 'css-loader' + } + ] + } + ] + }, + performance: { + hints: false + } +}; diff --git a/extensions/ql-vscode/gulpfile.js/webpack.ts b/extensions/ql-vscode/gulpfile.js/webpack.ts new file mode 100644 index 000000000..d165b5d10 --- /dev/null +++ b/extensions/ql-vscode/gulpfile.js/webpack.ts @@ -0,0 +1,27 @@ +import * as webpack from 'webpack'; +import { config } from './webpack.config'; + +export function compileView(cb: (err?: Error) => void) { + webpack(config).run((error, stats) => { + if (error) { + cb(error); + } + console.log(stats.toString({ + errorDetails: true, + colors: true, + assets: false, + builtAt: false, + version: false, + hash: false, + entrypoints: false, + timings: false, + modules: false + })); + if (stats.hasErrors()) { + cb(new Error('Compilation errors detected.')); + return; + } + + cb(); + }); +} diff --git a/extensions/ql-vscode/language-configuration.json b/extensions/ql-vscode/language-configuration.json new file mode 100644 index 000000000..7e1bb0e09 --- /dev/null +++ b/extensions/ql-vscode/language-configuration.json @@ -0,0 +1,72 @@ +{ + "comments": { + // symbol used for single line comment. Remove this entry if your language does not support line comments + "lineComment": "//", + // symbols used for start and end a block comment. Remove this entry if your language does not support block comments + "blockComment": [ + "/*", + "*/" + ] + }, + // symbols used as brackets + "brackets": [ + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ] + ], + // symbols that are auto closed when typing + "autoClosingPairs": [ + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + [ + "\"", + "\"" + ], + [ + "'", + "'" + ] + ], + // symbols that that can be used to surround a selection + "surroundingPairs": [ + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + [ + "\"", + "\"" + ], + [ + "'", + "'" + ] + ] +} \ No newline at end of file diff --git a/extensions/ql-vscode/media/VS-marketplace-CodeQL-icon.png b/extensions/ql-vscode/media/VS-marketplace-CodeQL-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8dd8556e8fa026b128f2f907e902ddd3a8131a4e GIT binary patch literal 508839 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

cw5LTv7hRz<-~O<2?{h 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

;CJw^m(Y6)MS5IrY_fmcKU&9(~14S#te`)~d!} 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

wH<0$z6v{pb<{P`>gqAjP38b6OXS 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 G`TZaH&*GZ^ literal 0 HcmV?d00001 diff --git a/extensions/ql-vscode/media/black-plus.svg b/extensions/ql-vscode/media/black-plus.svg new file mode 100644 index 000000000..9e7542c5f --- /dev/null +++ b/extensions/ql-vscode/media/black-plus.svg @@ -0,0 +1,58 @@ + +image/svg+xml \ No newline at end of file diff --git a/extensions/ql-vscode/media/check-dark-mode.svg b/extensions/ql-vscode/media/check-dark-mode.svg new file mode 100644 index 000000000..0685a867c --- /dev/null +++ b/extensions/ql-vscode/media/check-dark-mode.svg @@ -0,0 +1,56 @@ + +image/svg+xml \ No newline at end of file diff --git a/extensions/ql-vscode/media/check-light-mode.svg b/extensions/ql-vscode/media/check-light-mode.svg new file mode 100644 index 000000000..8ddd14da2 --- /dev/null +++ b/extensions/ql-vscode/media/check-light-mode.svg @@ -0,0 +1,57 @@ + +image/svg+xml + diff --git a/extensions/ql-vscode/media/logo.svg b/extensions/ql-vscode/media/logo.svg new file mode 100644 index 000000000..e88933f47 --- /dev/null +++ b/extensions/ql-vscode/media/logo.svg @@ -0,0 +1,14 @@ + + + + Slice + Created with Sketch. + + + + + + + + + \ No newline at end of file diff --git a/extensions/ql-vscode/media/red-x.svg b/extensions/ql-vscode/media/red-x.svg new file mode 100644 index 000000000..51714ed34 --- /dev/null +++ b/extensions/ql-vscode/media/red-x.svg @@ -0,0 +1,58 @@ + +image/svg+xml \ No newline at end of file diff --git a/extensions/ql-vscode/media/white-plus.svg b/extensions/ql-vscode/media/white-plus.svg new file mode 100644 index 000000000..a9db0dd47 --- /dev/null +++ b/extensions/ql-vscode/media/white-plus.svg @@ -0,0 +1,58 @@ + +image/svg+xml \ No newline at end of file diff --git a/extensions/ql-vscode/package.json b/extensions/ql-vscode/package.json new file mode 100644 index 000000000..450757dfb --- /dev/null +++ b/extensions/ql-vscode/package.json @@ -0,0 +1,344 @@ +{ + "name": "vscode-codeql", + "displayName": "CodeQL", + "description": "CodeQL for Visual Studio Code", + "author": "GitHub", + "private": true, + "version": "1.0.0", + "publisher": "GitHub", + "license": "MIT", + "icon": "media/VS-marketplace-CodeQL-icon.png", + "repository": { + "type": "git", + "url": "https://github.com/github/vscode-codeql" + }, + "engines": { + "vscode": "^1.39.0" + }, + "categories": [ + "Programming Languages" + ], + "activationEvents": [ + "onLanguage:ql", + "onView:codeQLDatabases", + "onView:codeQLQueryHistory", + "onCommand:codeQL.checkForUpdatesToCLI", + "onCommand:codeQL.chooseDatabase", + "onCommand:codeQL.setCurrentDatabase", + "onCommand:codeQLDatabases.chooseDatabase", + "onCommand:codeQLDatabases.setCurrentDatabase", + "onWebviewPanel:resultsView", + "onFileSystem:codeql-zip-archive" + ], + "main": "./out/extension", + "files": [ + "gen/*.js", + "media/**", + "out/**", + "package.json", + "language-configuration.json" + ], + "contributes": { + "languages": [ + { + "id": "ql", + "aliases": [ + "QL", + "ql", + "CodeQL" + ], + "extensions": [ + ".ql", + ".qll" + ], + "configuration": "./language-configuration.json" + }, + { + "id": "dbscheme", + "aliases": [ + "DBScheme", + "dbscheme" + ], + "extensions": [ + ".dbscheme" + ], + "configuration": "./language-configuration.json" + } + ], + "grammars": [ + { + "language": "ql", + "scopeName": "source.ql", + "path": "./out/syntaxes/ql.tmLanguage.json" + }, + { + "language": "dbscheme", + "scopeName": "source.dbscheme", + "path": "./out/syntaxes/dbscheme.tmLanguage.json" + } + ], + "configuration": { + "type": "object", + "title": "CodeQL", + "properties": { + "codeQL.cli.executablePath": { + "scope": "window", + "type": "string", + "default": "", + "description": "Path to the CodeQL executable that should be used by the CodeQL extension. The executable is named `codeql` on Linux/Mac and `codeql.cmd` on Windows. This overrides all other CodeQL CLI settings." + }, + "codeQL.runningQueries.numberOfThreads": { + "type": "integer", + "default": 1, + "minimum": 1, + "maximum": 1024, + "description": "Number of threads for running queries." + }, + "codeQL.runningQueries.timeout": { + "type": ["integer", "null"], + "default": null, + "minimum": 0, + "maximum": 2147483647, + "description": "Timeout (in seconds) for running queries. Leave blank or set to zero for no timeout." + }, + "codeQL.runningQueries.memory": { + "type": ["integer", "null"], + "default": null, + "minimum": 1024, + "description": "Memory (in MB) to use for running queries. Leave blank for CodeQL to choose a suitable value based on your system's available memory." + }, + "codeQL.runningQueries.debug": { + "type": "boolean", + "default": false, + "description": "Enable debug logging and tuple counting when running CodeQL queries. This information is useful for debugging query performance." + } + } + }, + "commands": [ + { + "command": "codeQL.runQuery", + "title": "CodeQL: Run Query" + }, + { + "command": "codeQL.quickEval", + "title": "CodeQL: Quick Evaluation" + }, + { + "command": "codeQL.chooseDatabase", + "title": "CodeQL: Choose Database", + "icon": { + "light": "media/black-plus.svg", + "dark": "media/white-plus.svg" + } + }, + { + "command": "codeQL.setCurrentDatabase", + "title": "CodeQL: Set Current Database" + }, + { + "command": "codeQL.upgradeCurrentDatabase", + "title": "CodeQL: Upgrade Current Database" + }, + { + "command": "codeQL.clearCache", + "title": "CodeQL: Clear Cache" + }, + { + "command": "codeQLDatabases.setCurrentDatabase", + "title": "Set Current Database" + }, + { + "command": "codeQLDatabases.removeDatabase", + "title": "Remove Database" + }, + { + "command": "codeQLDatabases.upgradeDatabase", + "title": "Upgrade Database" + }, + { + "command": "codeQL.checkForUpdatesToCLI", + "title": "CodeQL: Check for CLI Updates" + }, + { + "command": "codeQLQueryHistory.openQuery", + "title": "CodeQL: Open Query" + }, + { + "command": "codeQLQueryHistory.itemClicked", + "title": "Query History Item" + } + ], + "menus": { + "view/title": [ + { + "command": "codeQL.chooseDatabase", + "when": "view == codeQLDatabases", + "group": "navigation" + } + ], + "view/item/context": [ + { + "command": "codeQLDatabases.setCurrentDatabase", + "group": "inline", + "when": "view == codeQLDatabases" + }, + { + "command": "codeQLDatabases.removeDatabase", + "group": "9_qlCommands", + "when": "view == codeQLDatabases" + }, + { + "command": "codeQLDatabases.upgradeDatabase", + "group": "9_qlCommands", + "when": "view == codeQLDatabases" + }, + { + "command": "codeQLQueryHistory.openQuery", + "group": "9_qlCommands", + "when": "view == codeQLQueryHistory" + } + ], + "explorer/context": [ + { + "command": "codeQL.setCurrentDatabase", + "group": "9_qlCommands", + "when": "resourceScheme == codeql-zip-archive || explorerResourceIsFolder" + }, + { + "command": "codeQL.runQuery", + "group": "9_qlCommands", + "when": "resourceLangId == ql && resourceExtname == .ql" + } + ], + "commandPalette": [ + { + "command": "codeQL.runQuery", + "when": "resourceLangId == ql && resourceExtname == .ql" + }, + { + "command": "codeQL.quickEval", + "when": "editorLangId == ql" + }, + { + "command": "codeQL.setCurrentDatabase", + "when": "false" + }, + { + "command": "codeQLDatabases.setCurrentDatabase", + "when": "false" + }, + { + "command": "codeQLDatabases.removeDatabase", + "when": "false" + }, + { + "command": "codeQLQueryHistory.openQuery", + "when": "false" + }, + { + "command": "codeQLQueryHistory.itemClicked", + "when": "false" + } + ], + "editor/context": [ + { + "command": "codeQL.runQuery", + "when": "editorLangId == ql && resourceExtname == .ql" + }, + { + "command": "codeQL.quickEval", + "when": "editorLangId == ql" + } + ] + }, + "viewsContainers": { + "activitybar": [ + { + "id": "ql-container", + "title": "CodeQL", + "icon": "media/logo.svg" + } + ] + }, + "views": { + "ql-container": [ + { + "id": "codeQLDatabases", + "name": "Databases" + }, + { + "id": "codeQLQueryHistory", + "name": "Query History" + } + ] + } + }, + "scripts": { + "build": "gulp", + "watch": "npm-run-all -p watch:*", + "watch:extension": "tsc --watch", + "test": "mocha --exit -r ts-node/register test/pure-tests/**/*.ts", + "preintegration": "rm -rf ./out/vscode-tests && gulp", + "integration": "node ./out/vscode-tests/run-integration-tests.js", + "update-vscode": "node ./node_modules/vscode/bin/install", + "postinstall": "node ./node_modules/vscode/bin/install", + "format": "tsfmt -r" + }, + "dependencies": { + "classnames": "~2.2.6", + "fs-extra": "^8.1.0", + "glob-promise": "^3.4.0", + "node-fetch": "~2.6.0", + "react": "^16.8.6", + "react-dom": "^16.8.6", + "semmle-bqrs": "^0.0.1", + "semmle-io-node": "^0.0.1", + "semmle-vscode-utils": "^0.0.1", + "tmp": "^0.1.0", + "unzipper": "~0.10.5", + "vscode-jsonrpc": "^4.0.0", + "vscode-languageclient": "^5.2.1" + }, + "devDependencies": { + "@types/chai": "^4.1.7", + "@types/classnames": "~2.2.9", + "@types/fs-extra": "^8.0.0", + "@types/glob": "^7.1.1", + "@types/google-protobuf": "^3.2.7", + "@types/gulp": "^4.0.6", + "@types/jszip": "~3.1.6", + "@types/mocha": "~5.2.7", + "@types/node": "^12.0.8", + "@types/node-fetch": "~2.5.2", + "@types/react": "^16.8.17", + "@types/react-dom": "^16.8.4", + "@types/sarif": "~2.1.2", + "@types/tmp": "^0.1.0", + "@types/unzipper": "~0.10.0", + "@types/vscode": "^1.39.0", + "@types/webpack": "^4.32.1", + "@types/xml2js": "~0.4.4", + "build-tasks": "^0.0.1", + "chai": "^4.2.0", + "child-process-promise": "^2.2.1", + "css-loader": "~3.1.0", + "glob": "^7.1.4", + "gulp": "^4.0.2", + "gulp-sourcemaps": "^2.6.5", + "gulp-typescript": "^5.0.1", + "mocha": "~6.2.1", + "npm-run-all": "^4.1.5", + "style-loader": "~0.23.1", + "through2": "^3.0.1", + "ts-loader": "^5.4.5", + "ts-node": "^8.3.0", + "ts-protoc-gen": "^0.9.0", + "typescript": "^3.5.2", + "typescript-config": "^0.0.1", + "typescript-formatter": "^7.2.2", + "vsce": "^1.65.0", + "vscode-test": "^1.0.0", + "webpack": "^4.38.0", + "webpack-cli": "^3.3.2" + } +} diff --git a/extensions/ql-vscode/src/archive-filesystem-provider.ts b/extensions/ql-vscode/src/archive-filesystem-provider.ts new file mode 100644 index 000000000..5bbfeeade --- /dev/null +++ b/extensions/ql-vscode/src/archive-filesystem-provider.ts @@ -0,0 +1,308 @@ +import * as fs from 'fs-extra'; +import * as unzipper from 'unzipper'; +import * as vscode from 'vscode'; +import { logger } from './logging'; + +// All path operations in this file must be on paths *within* the zip +// archive. +import * as _path from 'path'; +const path = _path.posix; + +export class File implements vscode.FileStat { + type: vscode.FileType; + ctime: number; + mtime: number; + size: number; + + constructor(public name: string, public data: Uint8Array) { + this.type = vscode.FileType.File; + this.ctime = Date.now(); + this.mtime = Date.now(); + this.size = data.length; + this.name = name; + } +} + +export class Directory implements vscode.FileStat { + type: vscode.FileType; + ctime: number; + mtime: number; + size: number; + entries: Map = new Map(); + + constructor(public name: string) { + this.type = vscode.FileType.Directory; + this.ctime = Date.now(); + this.mtime = Date.now(); + this.size = 0; + } +} + +export type Entry = File | Directory; + +/** + * A map containing directory hierarchy information in a convenient form. + * + * For example, if dirMap : DirectoryHierarchyMap, and /foo/bar/baz.c is a file in the + * directory structure being represented, then + * + * dirMap['/foo'] = {'bar': vscode.FileType.Directory} + * dirMap['/foo/bar'] = {'baz': vscode.FileType.File} + */ +export type DirectoryHierarchyMap = Map>; + +export type ZipFileReference = { sourceArchiveZipPath: string, pathWithinSourceArchive: string }; + +/** Encodes a reference to a source file within a zipped source archive into a single URI. */ +export function encodeSourceArchiveUri(ref: ZipFileReference): vscode.Uri { + const { sourceArchiveZipPath, pathWithinSourceArchive } = ref; + + // These two paths are put into a single URI with a custom scheme. + // The path and authority components of the URI encode the two paths. + + // The path component of the URI contains both paths, joined by a slash. + let encodedPath = path.join(sourceArchiveZipPath, pathWithinSourceArchive); + + // If a URI contains an authority component, then the path component + // must either be empty or begin with a slash ("/") character. + // (Source: https://tools.ietf.org/html/rfc3986#section-3.3) + // Since we will use an authority component, we add a leading slash if necessary + // (paths on Windows usually start with the drive letter). + let sourceArchiveZipPathStartIndex: number; + if (encodedPath.startsWith('/')) { + sourceArchiveZipPathStartIndex = 0; + } else { + encodedPath = '/' + encodedPath; + sourceArchiveZipPathStartIndex = 1; + } + + // The authority component of the URI records the 0-based inclusive start and exclusive end index + // of the source archive zip path within the path component of the resulting URI. + // This lets us separate the paths, ignoring the leading slash if we added one. + const sourceArchiveZipPathEndIndex = sourceArchiveZipPathStartIndex + sourceArchiveZipPath.length; + const authority = `${sourceArchiveZipPathStartIndex}-${sourceArchiveZipPathEndIndex}`; + return vscode.Uri.parse(zipArchiveScheme + ':/').with({ + path: encodedPath, + authority, + }); +} + +const sourceArchiveUriAuthorityPattern = /^(\d+)\-(\d+)$/; + +class InvalidSourceArchiveUriError extends Error { + constructor(uri: vscode.Uri) { + super(`Can't decode uri ${uri}: authority should be of the form startIndex-endIndex (where both indices are integers).`); + } +} + +/** Decodes an encoded source archive URI into its corresponding paths. Inverse of `encodeSourceArchiveUri`. */ +export function decodeSourceArchiveUri(uri: vscode.Uri): ZipFileReference { + const match = sourceArchiveUriAuthorityPattern.exec(uri.authority); + if (match === null) + throw new InvalidSourceArchiveUriError(uri); + const zipPathStartIndex = parseInt(match[1]); + const zipPathEndIndex = parseInt(match[2]); + if (isNaN(zipPathStartIndex) || isNaN(zipPathEndIndex)) + throw new InvalidSourceArchiveUriError(uri); + return { + pathWithinSourceArchive: uri.path.substring(zipPathEndIndex), + sourceArchiveZipPath: uri.path.substring(zipPathStartIndex, zipPathEndIndex), + }; +} + +/** + * Make sure `file` and all of its parent directories are represented in `map`. + */ +function ensureFile(map: DirectoryHierarchyMap, file: string) { + const dirname = path.dirname(file); + if (dirname === '.') { + const error = `Ill-formed path ${file} in zip archive (expected absolute path)`; + logger.log(error); + throw new Error(error); + } + ensureDir(map, dirname); + map.get(dirname)!.set(path.basename(file), vscode.FileType.File); +} + +/** + * Make sure `dir` and all of its parent directories are represented in `map`. + */ +function ensureDir(map: DirectoryHierarchyMap, dir: string) { + const parent = path.dirname(dir); + if (!map.has(dir)) { + map.set(dir, new Map); + if (dir !== parent) { // not the root directory + ensureDir(map, parent); + map.get(parent)!.set(path.basename(dir), vscode.FileType.Directory); + } + } +} + +type Archive = { + unzipped: unzipper.CentralDirectory, + dirMap: DirectoryHierarchyMap, +}; + +export class ArchiveFileSystemProvider implements vscode.FileSystemProvider { + private readOnlyError = vscode.FileSystemError.NoPermissions('write operation attempted, but source archive filesystem is readonly'); + private archives: Map = new Map; + + private async getArchive(zipPath: string): Promise { + if (!this.archives.has(zipPath)) { + if (!await fs.pathExists(zipPath)) + throw vscode.FileSystemError.FileNotFound(zipPath); + const archive: Archive = { unzipped: await unzipper.Open.file(zipPath), dirMap: new Map }; + archive.unzipped.files.forEach(f => { ensureFile(archive.dirMap, path.resolve('/', f.path)); }); + this.archives.set(zipPath, archive); + } + return this.archives.get(zipPath)!; + } + + root = new Directory(''); + + // metadata + + async stat(uri: vscode.Uri): Promise { + return await this._lookup(uri, false); + } + + async readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> { + const ref = decodeSourceArchiveUri(uri); + const archive = await this.getArchive(ref.sourceArchiveZipPath); + let contents = archive.dirMap.get(ref.pathWithinSourceArchive); + const result = contents === undefined ? [] : Array.from(contents.entries()); + if (result === undefined) { + throw vscode.FileSystemError.FileNotFound(uri); + } + return result; + } + + // file contents + + async readFile(uri: vscode.Uri): Promise { + const data = (await this._lookupAsFile(uri, false)).data; + if (data) { + return data; + } + throw vscode.FileSystemError.FileNotFound(); + } + + // write operations, all disabled + + writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean, overwrite: boolean }): void { + throw this.readOnlyError; + } + + rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean }): void { + throw this.readOnlyError; + } + + delete(uri: vscode.Uri): void { + throw this.readOnlyError; + } + + createDirectory(uri: vscode.Uri): void { + throw this.readOnlyError; + } + + // content lookup + + private async _lookup(uri: vscode.Uri, silent: boolean): Promise { + const ref = decodeSourceArchiveUri(uri); + const archive = await this.getArchive(ref.sourceArchiveZipPath); + + // this is a path inside the archive, so don't use `.fsPath`, and + // use '/' as path separator throughout + const reqPath = ref.pathWithinSourceArchive; + + const file = archive.unzipped.files.find( + f => { + const absolutePath = path.resolve('/', f.path); + return absolutePath === reqPath + || absolutePath === path.join('/src_archive', reqPath); + } + ); + if (file !== undefined) { + if (file.type === 'File') { + return new File(reqPath, await file.buffer()); + } + else { // file.type === 'Directory' + // I haven't observed this case in practice. Could it happen + // with a zip file that contains empty directories? + return new Directory(reqPath); + } + } + if (archive.dirMap.has(reqPath)) { + return new Directory(reqPath); + } + throw vscode.FileSystemError.FileNotFound(uri); + } + + private async _lookupAsDirectory(uri: vscode.Uri, silent: boolean): Promise { + let entry = await this._lookup(uri, silent); + if (entry instanceof Directory) { + return entry; + } + throw vscode.FileSystemError.FileNotADirectory(uri); + } + + private async _lookupAsFile(uri: vscode.Uri, silent: boolean): Promise { + let entry = await this._lookup(uri, silent); + if (entry instanceof File) { + return entry; + } + throw vscode.FileSystemError.FileIsADirectory(uri); + } + + private _lookupParentDirectory(uri: vscode.Uri): Promise { + const dirname = uri.with({ path: path.dirname(uri.path) }); + return this._lookupAsDirectory(dirname, false); + } + + // file events + + private _emitter = new vscode.EventEmitter(); + private _bufferedEvents: vscode.FileChangeEvent[] = []; + private _fireSoonHandle?: NodeJS.Timer; + + readonly onDidChangeFile: vscode.Event = this._emitter.event; + + watch(_resource: vscode.Uri): vscode.Disposable { + // ignore, fires for all changes... + return new vscode.Disposable(() => { }); + } + + private _fireSoon(...events: vscode.FileChangeEvent[]): void { + this._bufferedEvents.push(...events); + + if (this._fireSoonHandle) { + clearTimeout(this._fireSoonHandle); + } + + this._fireSoonHandle = setTimeout(() => { + this._emitter.fire(this._bufferedEvents); + this._bufferedEvents.length = 0; + }, 5); + } +} + +/** + * Custom uri scheme for referring to files inside zip archives stored + * in the filesystem. See `encodeSourceArchiveUri`/`decodeSourceArchiveUri` for + * how these uris are constructed. + * + * (cf. https://www.ietf.org/rfc/rfc2396.txt (Appendix A, page 26) for + * the fact that hyphens are allowed in uri schemes) + */ +export const zipArchiveScheme = 'codeql-zip-archive'; + +export function activate(ctx: vscode.ExtensionContext) { + ctx.subscriptions.push(vscode.workspace.registerFileSystemProvider( + zipArchiveScheme, + new ArchiveFileSystemProvider(), + { + isCaseSensitive: true, + isReadonly: true, + } + )); +} diff --git a/extensions/ql-vscode/src/blob.d.ts b/extensions/ql-vscode/src/blob.d.ts new file mode 100644 index 000000000..093ea0594 --- /dev/null +++ b/extensions/ql-vscode/src/blob.d.ts @@ -0,0 +1,11 @@ +/** + * The npm library jszip is designed to work in both the browser and + * node. Consequently its typings @types/jszip refers to both node + * types like `Buffer` (which don't exist in the browser), and browser + * types like `Blob` (which don't exist in node). Instead of sticking + * all of `dom` in `compilerOptions.lib`, it suffices just to put in a + * stub definition of the type `Blob` here so that compilation + * succeeds. + */ + +declare type Blob = string; diff --git a/extensions/ql-vscode/src/cli-version.ts b/extensions/ql-vscode/src/cli-version.ts new file mode 100644 index 000000000..82ea67c46 --- /dev/null +++ b/extensions/ql-vscode/src/cli-version.ts @@ -0,0 +1,96 @@ +import { runCodeQlCliCommand } from "./cli"; +import { Logger } from "./logging"; + +/** + * Get the version of a CodeQL CLI. + */ +export async function getCodeQlCliVersion(codeQlPath: string, logger: Logger): Promise { + const output: string = await runCodeQlCliCommand( + codeQlPath, + ["version"], + ["--format=terse"], + "Checking CodeQL version", + logger + ); + return tryParseVersionString(output.trim()); +} + +/** + * Try to parse a version string, returning undefined if we can't parse it. + * + * Version strings must contain a major, minor, and patch version. They may optionally + * start with "v" and may optionally contain some "tail" string after the major, minor, and + * patch versions, for example as in `v2.1.0+baf5bff`. + */ +export function tryParseVersionString(versionString: string): Version | undefined { + const match = versionString.match(versionRegex); + if (match === null) { + return undefined; + } + return { + buildMetadata: match[5], + majorVersion: Number.parseInt(match[1], 10), + minorVersion: Number.parseInt(match[2], 10), + patchVersion: Number.parseInt(match[3], 10), + prereleaseVersion: match[4], + rawString: versionString, + } +} + +/** + * Regex for parsing semantic versions + * + * From the semver spec https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string + */ +const versionRegex = new RegExp(String.raw`^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + + String.raw`(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + + String.raw`(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`); + +/** + * A version of the CodeQL CLI. + */ +export interface Version { + /** + * Build metadata + * + * For example, this will be `abcdef0` for version 2.1.0-alpha.1+abcdef0. + * Build metadata must be ignored when comparing versions. + */ + buildMetadata: string | undefined; + + /** + * Major version number + * + * For example, this will be `2` for version 2.1.0-alpha.1+abcdef0. + */ + majorVersion: number; + + /** + * Minor version number + * + * For example, this will be `1` for version 2.1.0-alpha.1+abcdef0. + */ + minorVersion: number; + + /** + * Patch version number + * + * For example, this will be `0` for version 2.1.0-alpha.1+abcdef0. + */ + patchVersion: number; + + /** + * Prerelease version + * + * For example, this will be `alpha.1` for version 2.1.0-alpha.1+abcdef0. + * The prerelease version must be considered when comparing versions. + */ + prereleaseVersion: string | undefined; + + /** + * Raw version string + * + * For example, this will be `2.1.0-alpha.1+abcdef0` for version 2.1.0-alpha.1+abcdef0. + */ + rawString: string; +} diff --git a/extensions/ql-vscode/src/cli.ts b/extensions/ql-vscode/src/cli.ts new file mode 100644 index 000000000..60dad2a70 --- /dev/null +++ b/extensions/ql-vscode/src/cli.ts @@ -0,0 +1,496 @@ +import * as child_process from "child_process"; +import * as fs from 'fs-extra'; +import * as path from 'path'; +import * as sarif from 'sarif'; +import * as util from 'util'; +import { Logger, ProgressReporter } from "./logging"; +import { Disposable } from "vscode"; +import { DistributionProvider } from "./distribution"; +import { SortDirection } from "./interface-types"; +import { assertNever } from "./helpers-pure"; + +/** + * The version of the SARIF format that we are using. + */ +const SARIF_FORMAT = "sarifv2.1.0"; + +/** + * Flags to pass to all cli commands. + */ +const LOGGING_FLAGS = ['-v', '--log-to-stderr']; + +/** + * The expected output of `codeql resolve library-path`. + */ +export interface QuerySetup { + libraryPath: string[], + dbscheme: string, + relativeName?: string, + compilationCache?: string +} + +/** + * The expected output of `codeql resolve database`. + */ +export interface DbInfo { + sourceLocationPrefix: string; + columnKind: string; + unicodeNewlines: boolean; + sourceArchiveZip: string; + sourceArchiveRoot: string; + datasetFolder: string; + logsFolder: string; +} + +/** + * The expected output of `codeql resolve upgrades`. + */ +export interface UpgradesInfo { + scripts: string[]; + finalDbscheme: string; +} + +/** + * The expected output of `codeql resolve metadata`. + */ +export interface QueryMetadata { + name?: string, + description?: string, + id?: string, + kind?: string +} + +// `codeql bqrs interpret` requires both of these to be present or +// both absent. +export interface SourceInfo { + sourceArchive: string; + sourceLocationPrefix: string; +} + +/** + * This class manages a cli server started by `codeql execute cli-server` to + * run commands without the overhead of starting a new java + * virtual machine each time. This class also controls access to the server + * by queueing the commands sent to it. + */ +export class CodeQLCliServer implements Disposable { + + /** The process for the cli server, or undefined if one doesn't exist yet */ + process?: child_process.ChildProcessWithoutNullStreams; + /** Queue of future commands*/ + commandQueue: (() => void)[]; + /** Whether a command is running */ + commandInProcess: boolean; + /** A buffer with a single null byte. */ + nullBuffer: Buffer; + + constructor(private config: DistributionProvider, private logger: Logger) { + this.commandQueue = []; + this.commandInProcess = false; + this.nullBuffer = Buffer.alloc(1); + if (this.config.onDidChangeDistribution) { + this.config.onDidChangeDistribution(() => { + this.restartCliServer(); + }); + } + } + + + dispose() { + this.killProcessIfRunning(); + } + + killProcessIfRunning() { + if (this.process) { + // Tell the Java CLI server process to shut down. + this.logger.log('Sending shutdown request'); + try { + this.process.stdin.write(JSON.stringify(["shutdown"]), "utf8"); + this.process.stdin.write(this.nullBuffer); + this.logger.log('Sent shutdown request'); + } catch (e) { + // We are probably fine here, the process has already closed stdin. + this.logger.log(`Shutdown request failed: process stdin may have already closed. The error was ${e}`); + this.logger.log('Stopping the process anyway.'); + } + // Close the stdin and stdout streams. + // This is important on Windows where the child process may not die cleanly. + this.process.stdin.end(); + this.process.kill(); + this.process.stdout.destroy(); + this.process.stderr.destroy(); + this.process = undefined; + + } + } + + /** + * Restart the server when the current command terminates + */ + private restartCliServer() { + let callback = () => { + try { + this.killProcessIfRunning(); + } finally { + this.runNext(); + } + }; + + // If the server is not running a command run this immediately + // otherwise add to the front of the queue (as we want to run this after the next command()). + if (this.commandInProcess) { + this.commandQueue.unshift(callback) + } else { + callback(); + } + + } + + /** + * Launch the cli server + */ + private async launchProcess(): Promise { + const config = await this.config.getCodeQlPathWithoutVersionCheck(); + if (!config) { + throw new Error("Failed to find codeql distribution") + } + return spawnServer(config, "CodeQL CLI Server", ["execute", "cli-server"], [], this.logger, data => { }) + } + + private async runCodeQlCliInternal(command: string[], commandArgs: string[], description: string): Promise { + let stderrBuffers: Buffer[] = []; + if (this.commandInProcess) { + throw new Error("runCodeQlCliInternal called while cli was running") + } + this.commandInProcess = true; + try { + //Launch the process if it doesn't exist + if (!this.process) { + this.process = await this.launchProcess() + } + // Grab the process so that typescript know that it is always defined. + const process = this.process; + // The array of fragments of stdout + let stdoutBuffers: Buffer[] = []; + + // Compute the full args array + const args = command.concat(LOGGING_FLAGS).concat(commandArgs); + const argsString = args.join(" "); + this.logger.log(`${description} using CodeQL CLI: ${argsString}...`); + try { + await new Promise((resolve, reject) => { + // Start listening to stdout + process.stdout.addListener('data', (newData: Buffer) => { + stdoutBuffers.push(newData); + // If the buffer ends in '0' then exit. + // We don't have to check the middle as no output will be written after the null until + // the next command starts + if (newData.length > 0 && newData.readUInt8(newData.length - 1) === 0) { + resolve(); + } + }); + // Listen to stderr + process.stderr.addListener('data', (newData: Buffer) => { + stderrBuffers.push(newData); + }); + // Listen for process exit. + process.addListener("close", (code) => reject(code)); + // Write the command followed by a null terminator. + process.stdin.write(JSON.stringify(args), "utf8") + process.stdin.write(this.nullBuffer) + }); + // Join all the data together + let fullBuffer = Buffer.concat(stdoutBuffers); + // Make sure we remove the terminator; + let data = fullBuffer.toString("utf8", 0, fullBuffer.length - 1); + this.logger.log(`CLI command succeeded.`); + return data; + } catch (err) { + // Kill the process if it isn't already dead. + this.killProcessIfRunning(); + // Report the error (if there is a stderr then use that otherwise just report the error cod or nodejs error) + if (stderrBuffers.length == 0) { + throw new Error(`${description} failed: ${err}`) + } else { + throw new Error(`${description} failed: ${Buffer.concat(stderrBuffers).toString("utf8")}`); + } + } finally { + this.logger.log(Buffer.concat(stderrBuffers).toString("utf8")); + // Remove the listeners we set up. + process.stdout.removeAllListeners('data') + process.stderr.removeAllListeners('data') + process.removeAllListeners("close"); + } + } finally { + this.commandInProcess = false; + // start running the next command immediately + this.runNext(); + } + } + + /** + * Run the next command in the queue + */ + private runNext() { + const callback = this.commandQueue.shift(); + if (callback) { + callback(); + } + } + + /** + * Runs a CodeQL CLI command on the server, returning the output as a string. + * @param command The `codeql` command to be run, provided as an array of command/subcommand names. + * @param commandArgs The arguments to pass to the `codeql` command. + * @param description Description of the action being run, to be shown in log and error messages. + * @param progressReporter Used to output progress messages, e.g. to the status bar. + * @returns The contents of the command's stdout, if the command succeeded. + */ + runCodeQlCliCommand(command: string[], commandArgs: string[], description: string, progressReporter?: ProgressReporter): Promise { + if (progressReporter) { + progressReporter.report({ message: description }); + } + + return new Promise((resolve, reject) => { + // Construct the command that actually does the work + const callback = () => { + try { + this.runCodeQlCliInternal(command, commandArgs, description).then(resolve, reject); + } catch (err) { + reject(err); + } + } + // If the server is not running a command, then run the given command immediately, + // otherwise add to the queue + if (this.commandInProcess) { + this.commandQueue.push(callback) + } else { + callback(); + } + }); + } + + /** + * Runs a CodeQL CLI command, returning the output as JSON. + * @param command The `codeql` command to be run, provided as an array of command/subcommand names. + * @param commandArgs The arguments to pass to the `codeql` command. + * @param description Description of the action being run, to be shown in log and error messages. + * @param progressReporter Used to output progress messages, e.g. to the status bar. + * @returns The contents of the command's stdout, if the command succeeded. + */ + async runJsonCodeQlCliCommand(command: string[], commandArgs: string[], description: string, progressReporter?: ProgressReporter): Promise { + // Add format argument first, in case commandArgs contains positional parameters. + const args = ['--format', 'json'].concat(commandArgs); + const result = await this.runCodeQlCliCommand(command, args, description, progressReporter); + try { + return JSON.parse(result) as OutputType; + } catch (err) { + throw new Error(`Parsing output of ${description} failed: ${err.stderr || err}`) + } + } + + /** + * Resolve the library path and dbscheme for a query. + * @param workspaces The current open workspaces + * @param queryPath The path to the query + */ + async resolveLibraryPath(workspaces: string[], queryPath: string): Promise { + const subcommandArgs = [ + '--query', queryPath, + "--additional-packs", + workspaces.join(path.delimiter) + ]; + return await this.runJsonCodeQlCliCommand(['resolve', 'library-path'], subcommandArgs, "Resolving library paths"); + } + + /** + * Gets the metadata for a query. + * @param queryPath The path to the query. + */ + async resolveMetadata(queryPath: string): Promise { + return await this.runJsonCodeQlCliCommand(['resolve', 'metadata'], [queryPath], "Resolving query metadata"); + } + + /** + * Gets the RAM setting for the query server. + * @param queryMemoryMb The maximum amount of RAM to use, in MB. + * Leave `undefined` for CodeQL to choose a limit based on the available system memory. + * @returns String arguments that can be passed to the CodeQL query server, + * indicating how to split the given RAM limit between heap and off-heap memory. + */ + async resolveRam(queryMemoryMb: number | undefined, progressReporter?: ProgressReporter): Promise { + const args: string[] = []; + if (queryMemoryMb !== undefined) { + args.push('--ram', queryMemoryMb.toString()); + } + return await this.runJsonCodeQlCliCommand(['resolve', 'ram'], args, "Resolving RAM settings", progressReporter); + } + + + async interpretBqrs(metadata: { kind: string, id: string }, resultsPath: string, interpretedResultsPath: string, sourceInfo?: SourceInfo): Promise { + const args = [ + `-t=kind=${metadata.kind}`, + `-t=id=${metadata.id}`, + "--output", interpretedResultsPath, + "--format", SARIF_FORMAT, + // TODO: This flag means that we don't group interpreted results + // by primary location. We may want to revisit whether we call + // interpretation with and without this flag, or do some + // grouping client-side. + "--no-group-results", + ]; + if (sourceInfo !== undefined) { + args.push( + "--source-archive", sourceInfo.sourceArchive, + "--source-location-prefix", sourceInfo.sourceLocationPrefix + ); + } + args.push(resultsPath); + await this.runCodeQlCliCommand(['bqrs', 'interpret'], args, "Interpreting query results"); + + let output: string; + try { + output = await fs.readFile(interpretedResultsPath, 'utf8'); + } catch (err) { + throw new Error(`Reading output of interpretation failed: ${err.stderr || err}`) + } + try { + return JSON.parse(output) as sarif.Log; + } catch (err) { + throw new Error(`Parsing output of interpretation failed: ${err.stderr || err}`) + } + } + + + async sortBqrs(resultsPath: string, sortedResultsPath: string, resultSet: string, sortKeys: number[], sortDirections: SortDirection[]): Promise { + const sortDirectionStrings = sortDirections.map(direction => { + switch (direction) { + case SortDirection.asc: + return "asc"; + case SortDirection.desc: + return "desc"; + default: + return assertNever(direction); + } + }); + + await this.runCodeQlCliCommand(['bqrs', 'decode'], + [ + "--format=bqrs", + `--result-set=${resultSet}`, + `--output=${sortedResultsPath}`, + `--sort-key=${sortKeys.join(",")}`, + `--sort-direction=${sortDirectionStrings.join(",")}`, + resultsPath + ], + "Sorting query results"); + } + + + /** + * Returns the `DbInfo` for a database. + * @param databasePath Path to the CodeQL database to obtain information from. + */ + resolveDatabase(databasePath: string): Promise { + return this.runJsonCodeQlCliCommand(['resolve', 'database'], [databasePath], + "Resolving database"); + } + + + /** + * Gets information necessary for upgrading a database. + * @param dbScheme the path to the dbscheme of the database to be upgraded. + * @param searchPath A list of directories to search for upgrade scripts. + * @returns A list of database upgrade script directories + */ + resolveUpgrades(dbScheme: string, searchPath: string[]): Promise { + const args = ['--additional-packs', searchPath.join(path.delimiter), '--dbscheme', dbScheme]; + + return this.runJsonCodeQlCliCommand( + ['resolve', 'upgrades'], + args, + "Resolving database upgrade scripts", + ); + } +} + +/** + * Spawns a child server process using the CodeQL CLI + * and attaches listeners to it. + * + * @param config The configuration containing the path to the CLI. + * @param name Name of the server being started, to be shown in log and error messages. + * @param command The `codeql` command to be run, provided as an array of command/subcommand names. + * @param commandArgs The arguments to pass to the `codeql` command. + * @param logger Logger to write startup messages. + * @param stderrListener Listener for log messages from the server's stderr stream. + * @param stdoutListener Optional listener for messages from the server's stdout stream. + * @param progressReporter Used to output progress messages, e.g. to the status bar. + * @returns The started child process. + */ +export function spawnServer( + codeqlPath: string, + name: string, + command: string[], + commandArgs: string[], + logger: Logger, + stderrListener: (data: any) => void, + stdoutListener?: (data: any) => void, + progressReporter?: ProgressReporter +): child_process.ChildProcessWithoutNullStreams { + // Enable verbose logging. + const args = command.concat(commandArgs).concat(LOGGING_FLAGS); + + // Start the server process. + const base = codeqlPath; + const argsString = args.join(" "); + if (progressReporter !== undefined) { + progressReporter.report({ message: `Starting ${name}` }); + } + logger.log(`Starting ${name} using CodeQL CLI: ${base} ${argsString}`); + const child = child_process.spawn(base, args); + if (!child || !child.pid) { + throw new Error(`Failed to start ${name} using command ${base} ${argsString}.`); + } + + // Set up event listeners. + child.on('close', (code) => logger.log(`Child process exited with code ${code}`)); + child.stderr!.on('data', stderrListener); + if (stdoutListener !== undefined) { + child.stdout!.on('data', stdoutListener); + } + + if (progressReporter !== undefined) { + progressReporter.report({ message: `Started ${name}` }); + } + logger.log(`${name} started on PID: ${child.pid}`); + return child; +} + +/** + * Runs a CodeQL CLI command without invoking the CLI server, returning the output as a string. + * @param config The configuration containing the path to the CLI. + * @param command The `codeql` command to be run, provided as an array of command/subcommand names. + * @param commandArgs The arguments to pass to the `codeql` command. + * @param description Description of the action being run, to be shown in log and error messages. + * @param logger Logger to write command log messages, e.g. to an output channel. + * @param progressReporter Used to output progress messages, e.g. to the status bar. + * @returns The contents of the command's stdout, if the command succeeded. + */ +export async function runCodeQlCliCommand(codeQlPath: string, command: string[], commandArgs: string[], description: string, logger: Logger, progressReporter?: ProgressReporter): Promise { + // Add logging arguments first, in case commandArgs contains positional parameters. + const args = command.concat(LOGGING_FLAGS).concat(commandArgs); + const argsString = args.join(" "); + try { + if (progressReporter !== undefined) { + progressReporter.report({ message: description }); + } + logger.log(`${description} using CodeQL CLI: ${codeQlPath} ${argsString}...`); + const result = await util.promisify(child_process.execFile)(codeQlPath, args); + logger.log(result.stderr); + logger.log(`CLI command succeeded.`); + return result.stdout; + } catch (err) { + throw new Error(`${description} failed: ${err.stderr || err}`) + } +} diff --git a/extensions/ql-vscode/src/config.ts b/extensions/ql-vscode/src/config.ts new file mode 100644 index 000000000..d8e9d6450 --- /dev/null +++ b/extensions/ql-vscode/src/config.ts @@ -0,0 +1,188 @@ +import { DisposableObject } from 'semmle-vscode-utils'; +import { workspace, Event, EventEmitter, ConfigurationChangeEvent } from 'vscode'; +import { DistributionManager } from './distribution'; +import { logger } from './logging'; + +/** Helper class to look up a labelled (and possibly nested) setting. */ +class Setting { + name: string; + parent?: Setting; + + constructor(name: string, parent?: Setting) { + this.name = name; + this.parent = parent; + } + + get qualifiedName(): string { + if (this.parent === undefined) { + return this.name; + } else { + return `${this.parent.qualifiedName}.${this.name}`; + } + } + + getValue(): T { + if (this.parent === undefined) { + throw new Error('Cannot get the value of a root setting.'); + } + return workspace.getConfiguration(this.parent.qualifiedName).get(this.name)!; + } +} + +const ROOT_SETTING = new Setting('codeQL'); + +// Distribution configuration + +const DISTRIBUTION_SETTING = new Setting('cli', ROOT_SETTING); +const CUSTOM_CODEQL_PATH_SETTING = new Setting('executablePath', DISTRIBUTION_SETTING); +const INCLUDE_PRERELEASE_SETTING = new Setting('includePrerelease', DISTRIBUTION_SETTING); +const PERSONAL_ACCESS_TOKEN_SETTING = new Setting('personalAccessToken', DISTRIBUTION_SETTING); +const OWNER_NAME_SETTING = new Setting('owner', DISTRIBUTION_SETTING); +const REPOSITORY_NAME_SETTING = new Setting('repository', DISTRIBUTION_SETTING); + +/** When these settings change, the distribution should be updated. */ +const DISTRIBUTION_CHANGE_SETTINGS = [CUSTOM_CODEQL_PATH_SETTING, INCLUDE_PRERELEASE_SETTING, PERSONAL_ACCESS_TOKEN_SETTING, OWNER_NAME_SETTING, REPOSITORY_NAME_SETTING]; + +export interface DistributionConfig { + customCodeQlPath?: string; + includePrerelease: boolean; + personalAccessToken?: string; + ownerName: string; + repositoryName: string; + onDidChangeDistributionConfiguration?: Event; +} + +// Query server configuration + +const RUNNING_QUERIES_SETTING = new Setting('runningQueries', ROOT_SETTING); +const NUMBER_OF_THREADS_SETTING = new Setting('numberOfThreads', RUNNING_QUERIES_SETTING); +const TIMEOUT_SETTING = new Setting('timeout', RUNNING_QUERIES_SETTING); +const MEMORY_SETTING = new Setting('memory', RUNNING_QUERIES_SETTING); +const DEBUG_SETTING = new Setting('debug', RUNNING_QUERIES_SETTING); + +/** When these settings change, the running query server should be restarted. */ +const QUERY_SERVER_RESTARTING_SETTINGS = [NUMBER_OF_THREADS_SETTING, MEMORY_SETTING, DEBUG_SETTING]; + +export interface QueryServerConfig { + codeQlPath: string, + debug: boolean, + numThreads: number, + queryMemoryMb?: number, + timeoutSecs: number, + onDidChangeQueryServerConfiguration?: Event; +} + +abstract class ConfigListener extends DisposableObject { + protected readonly _onDidChangeConfiguration = this.push(new EventEmitter()); + + constructor() { + super(); + this.updateConfiguration(); + this.push(workspace.onDidChangeConfiguration(this.handleDidChangeConfiguration, this)); + } + + /** + * Calls `updateConfiguration` if any of the `relevantSettings` have changed. + */ + protected handleDidChangeConfigurationForRelevantSettings(relevantSettings: Setting[], e: ConfigurationChangeEvent): void { + // Check whether any options that affect query running were changed. + for (const option of relevantSettings) { + // TODO: compare old and new values, only update if there was actually a change? + if (e.affectsConfiguration(option.qualifiedName)) { + this.updateConfiguration(); + break; // only need to do this once, if any of the settings have changed + } + } + } + + protected abstract handleDidChangeConfiguration(e: ConfigurationChangeEvent): void; + private updateConfiguration(): void { + this._onDidChangeConfiguration.fire(); + } +} + +export class DistributionConfigListener extends ConfigListener implements DistributionConfig { + public get customCodeQlPath(): string | undefined { + return CUSTOM_CODEQL_PATH_SETTING.getValue() ? CUSTOM_CODEQL_PATH_SETTING.getValue() : undefined; + } + + public get includePrerelease(): boolean { + return INCLUDE_PRERELEASE_SETTING.getValue(); + } + + public get personalAccessToken(): string | undefined { + return PERSONAL_ACCESS_TOKEN_SETTING.getValue() ? PERSONAL_ACCESS_TOKEN_SETTING.getValue() : undefined; + } + + public get ownerName(): string { + return OWNER_NAME_SETTING.getValue(); + } + + public get repositoryName(): string { + return REPOSITORY_NAME_SETTING.getValue(); + } + + public get onDidChangeDistributionConfiguration(): Event { + return this._onDidChangeConfiguration.event; + } + + protected handleDidChangeConfiguration(e: ConfigurationChangeEvent): void { + this.handleDidChangeConfigurationForRelevantSettings(DISTRIBUTION_CHANGE_SETTINGS, e); + } +} + +export class QueryServerConfigListener extends ConfigListener implements QueryServerConfig { + private constructor(private _codeQlPath: string) { + super(); + } + + public static async createQueryServerConfigListener(distributionManager: DistributionManager): Promise { + const codeQlPath = await distributionManager.getCodeQlPathWithoutVersionCheck(); + const config = new QueryServerConfigListener(codeQlPath!); + if (distributionManager.onDidChangeDistribution) { + config.push(distributionManager.onDidChangeDistribution(async () => { + const codeQlPath = await distributionManager.getCodeQlPathWithoutVersionCheck(); + config._codeQlPath = codeQlPath!; + config._onDidChangeConfiguration.fire(); + })); + } + return config; + } + + public get codeQlPath(): string { + return this._codeQlPath; + } + + public get numThreads(): number { + return NUMBER_OF_THREADS_SETTING.getValue(); + } + + /** Gets the configured query timeout, in seconds. This looks up the setting at the time of access. */ + public get timeoutSecs(): number { + return TIMEOUT_SETTING.getValue() || 0; + } + + public get queryMemoryMb(): number | undefined { + const memory = MEMORY_SETTING.getValue(); + if (memory === null) { + return undefined; + } + if (memory == 0 || typeof (memory) !== 'number') { + logger.log(`Ignoring value '${memory}' for setting ${MEMORY_SETTING.qualifiedName}`); + return undefined; + } + return memory; + } + + public get debug(): boolean { + return DEBUG_SETTING.getValue(); + } + + public get onDidChangeQueryServerConfiguration(): Event { + return this._onDidChangeConfiguration.event; + } + + protected handleDidChangeConfiguration(e: ConfigurationChangeEvent): void { + this.handleDidChangeConfigurationForRelevantSettings(QUERY_SERVER_RESTARTING_SETTINGS, e); + } +} diff --git a/extensions/ql-vscode/src/databases-ui.ts b/extensions/ql-vscode/src/databases-ui.ts new file mode 100644 index 000000000..a8cb0767f --- /dev/null +++ b/extensions/ql-vscode/src/databases-ui.ts @@ -0,0 +1,263 @@ +import * as path from 'path'; +import { DisposableObject } from "semmle-vscode-utils"; +import { commands, Event, EventEmitter, ExtensionContext, ProviderResult, TreeDataProvider, TreeItem, Uri, window } from "vscode"; +import * as cli from './cli'; +import { DatabaseItem, DatabaseManager } from "./databases"; +import { logger } from "./logging"; +import { clearCacheInDatabase, upgradeDatabase, UserCancellationException } from "./queries"; +import * as qsClient from './queryserver-client'; +import { getOnDiskWorkspaceFolders } from "./helpers"; + +type ThemableIconPath = { light: string, dark: string } | string; + +/** + * Path to icons to display next to currently selected database. + */ +const SELECTED_DATABASE_ICON: ThemableIconPath = { + light: 'media/check-light-mode.svg', + dark: 'media/check-dark-mode.svg', +}; + +/** + * Path to icon to display next to an invalid database. + */ +const INVALID_DATABASE_ICON: ThemableIconPath = 'media/red-x.svg'; + +function joinThemableIconPath(base: string, iconPath: ThemableIconPath): ThemableIconPath { + if (typeof iconPath == 'object') + return { + light: path.join(base, iconPath.light), + dark: path.join(base, iconPath.dark) + }; + else + return path.join(base, iconPath); +} + +/** + * Tree data provider for the databases view. + */ +class DatabaseTreeDataProvider extends DisposableObject + implements TreeDataProvider { + + private readonly _onDidChangeTreeData = new EventEmitter(); + private currentDatabaseItem: DatabaseItem | undefined; + + constructor(private ctx: ExtensionContext, private databaseManager: DatabaseManager) { + super(); + + this.currentDatabaseItem = databaseManager.currentDatabaseItem; + + this.push(this.databaseManager.onDidChangeDatabaseItem(this.handleDidChangeDatabaseItem)); + this.push(this.databaseManager.onDidChangeCurrentDatabaseItem( + this.handleDidChangeCurrentDatabaseItem)); + } + + public get onDidChangeTreeData(): Event { + return this._onDidChangeTreeData.event; + } + + private handleDidChangeDatabaseItem = (databaseItem: DatabaseItem | undefined): void => { + this._onDidChangeTreeData.fire(databaseItem); + } + + private handleDidChangeCurrentDatabaseItem = (databaseItem: DatabaseItem | undefined): void => { + if (this.currentDatabaseItem) { + this._onDidChangeTreeData.fire(this.currentDatabaseItem); + } + this.currentDatabaseItem = databaseItem; + if (this.currentDatabaseItem) { + this._onDidChangeTreeData.fire(this.currentDatabaseItem); + } + } + + public getTreeItem(element: DatabaseItem): TreeItem { + const item = new TreeItem(element.name); + if (element === this.currentDatabaseItem) { + item.iconPath = joinThemableIconPath(this.ctx.extensionPath, SELECTED_DATABASE_ICON); + } else if (element.error !== undefined) { + item.iconPath = joinThemableIconPath(this.ctx.extensionPath, INVALID_DATABASE_ICON); + } + item.tooltip = element.databaseUri.fsPath; + return item; + } + + public getChildren(element?: DatabaseItem): ProviderResult { + if (element === undefined) { + return this.databaseManager.databaseItems.slice(0); + } + else { + return []; + } + } + + public getParent(element: DatabaseItem): ProviderResult { + return null; + } + + public getCurrent(): DatabaseItem | undefined { + return this.currentDatabaseItem; + } +} + +/** Gets the first element in the given list, if any, or undefined if the list is empty or undefined. */ +function getFirst(list: Uri[] | undefined): Uri | undefined { + if (list === undefined || list.length === 0) { + return undefined; + } + else { + return list[0]; + } +} + +/** + * Displays file selection dialog. Expects the user to choose a + * database directory, which should be the parent directory of a + * directory of the form `db-[language]`, for example, `db-cpp`. + * + * XXX: no validation is done other than checking the directory name + * to make sure it really is a database directory. + */ +async function chooseDatabaseDir(): Promise { + const chosen = await window.showOpenDialog({ + openLabel: 'Choose Database', + canSelectFiles: true, + canSelectFolders: true, + canSelectMany: false + }); + return getFirst(chosen); +} + +export class DatabaseUI extends DisposableObject { + public constructor(private ctx: ExtensionContext, private cliserver: cli.CodeQLCliServer, private databaseManager: DatabaseManager, + private readonly queryServer: qsClient.QueryServerClient | undefined) { + + super(); + + const treeDataProvider = this.push(new DatabaseTreeDataProvider(ctx, databaseManager)); + this.push(window.createTreeView('codeQLDatabases', { treeDataProvider })); + + ctx.subscriptions.push(commands.registerCommand('codeQL.chooseDatabase', this.handleChooseDatabase)); + ctx.subscriptions.push(commands.registerCommand('codeQL.setCurrentDatabase', this.handleSetCurrentDatabase)); + ctx.subscriptions.push(commands.registerCommand('codeQL.upgradeCurrentDatabase', this.handleUpgradeCurrentDatabase)); + ctx.subscriptions.push(commands.registerCommand('codeQL.clearCache', this.handleClearCache)); + ctx.subscriptions.push(commands.registerCommand('codeQLDatabases.setCurrentDatabase', this.handleMakeCurrentDatabase)); + ctx.subscriptions.push(commands.registerCommand('codeQLDatabases.removeDatabase', this.handleRemoveDatabase)); + ctx.subscriptions.push(commands.registerCommand('codeQLDatabases.upgradeDatabase', this.handleUpgradeDatabase)); + } + + private handleMakeCurrentDatabase = async (databaseItem: DatabaseItem): Promise => { + await this.databaseManager.setCurrentDatabaseItem(databaseItem); + } + + private handleChooseDatabase = async (): Promise => { + return await this.chooseAndSetDatabase(); + } + + private handleUpgradeCurrentDatabase = async (): Promise => { + await this.handleUpgradeDatabase(this.databaseManager.currentDatabaseItem); + } + + private handleUpgradeDatabase = async (databaseItem: DatabaseItem | undefined): Promise => { + if (this.queryServer === undefined) { + logger.log('Received request to upgrade database, but there is no running query server.'); + return; + } + if (databaseItem === undefined) { + logger.log('Received request to upgrade database, but no database was provided.'); + return; + } + if (databaseItem.contents === undefined) { + logger.log('Received request to upgrade database, but database contents could not be found.'); + return; + } + if (databaseItem.contents.dbSchemeUri === undefined) { + logger.log('Received request to upgrade database, but database has no schema.'); + return; + } + + // Search for upgrade scripts in any workspace folders available + const searchPath: string[] = getOnDiskWorkspaceFolders(); + + const upgradeInfo = await this.cliserver.resolveUpgrades( + databaseItem.contents.dbSchemeUri.fsPath, + searchPath, + ); + + const { scripts, finalDbscheme } = upgradeInfo; + + if (finalDbscheme === undefined) { + logger.log('Could not determine target dbscheme to upgrade to.'); + return; + } + + const parentDirs = scripts.map(dir => path.dirname(dir)); + const uniqueParentDirs = new Set(parentDirs); + const targetDbSchemeUri = Uri.file(finalDbscheme); + + + const upgradesDirectories = Array.from(uniqueParentDirs).map(filePath => Uri.file(filePath)); + try { + await upgradeDatabase(this.queryServer, databaseItem, targetDbSchemeUri, upgradesDirectories); + } + catch (e) { + if (e instanceof UserCancellationException) { + logger.log(e.message); + } + else + throw e; + } + } + + private handleClearCache = async (): Promise => { + if ((this.queryServer !== undefined) && + (this.databaseManager.currentDatabaseItem !== undefined)) { + + await clearCacheInDatabase(this.queryServer, this.databaseManager.currentDatabaseItem); + } + } + + private handleSetCurrentDatabase = async (uri: Uri): Promise => { + return await this.setCurrentDatabase(uri); + } + + private handleRemoveDatabase = (databaseItem: DatabaseItem): void => { + this.databaseManager.removeDatabaseItem(databaseItem); + } + + /** + * Return the current database directory. If we don't already have a + * current database, ask the user for one, and return that, or + * undefined if they cancel. + */ + public async getDatabaseItem(): Promise { + if (this.databaseManager.currentDatabaseItem === undefined) { + await this.chooseAndSetDatabase(); + } + + return this.databaseManager.currentDatabaseItem; + } + + private async setCurrentDatabase(uri: Uri): Promise { + let databaseItem = this.databaseManager.findDatabaseItem(uri); + if (databaseItem === undefined) { + databaseItem = await this.databaseManager.openDatabase(uri); + } + await this.databaseManager.setCurrentDatabaseItem(databaseItem); + + return databaseItem; + } + + /** + * Ask the user for a database directory. Returns the chosen database, or `undefined` if the + * operation was canceled. + */ + private async chooseAndSetDatabase(): Promise { + const uri = await chooseDatabaseDir(); + if (uri !== undefined) { + return await this.setCurrentDatabase(uri); + } + else { + return undefined; + } + } +} diff --git a/extensions/ql-vscode/src/databases.ts b/extensions/ql-vscode/src/databases.ts new file mode 100644 index 000000000..9348d13eb --- /dev/null +++ b/extensions/ql-vscode/src/databases.ts @@ -0,0 +1,631 @@ +import * as fs from 'fs-extra'; +import * as glob from 'glob-promise'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import * as cli from './cli'; +import { ExtensionContext } from 'vscode'; +import { showAndLogErrorMessage, showAndLogWarningMessage, showAndLogInformationMessage } from './helpers'; +import { zipArchiveScheme, encodeSourceArchiveUri, decodeSourceArchiveUri } from './archive-filesystem-provider'; +import { DisposableObject } from 'semmle-vscode-utils'; +import { QueryServerConfig } from './config'; +import { Logger, logger } from './logging'; + +/** + * databases.ts + * ------------ + * Managing state of what the current database is, and what other + * databases have been recently selected. + * + * The source of truth of the current state resides inside the + * `DatabaseManager` class below. + */ + +/** + * The name of the key in the workspaceState dictionary in which we + * persist the current database across sessions. + */ +const CURRENT_DB: string = 'currentDatabase'; + +/** + * The name of the key in the workspaceState dictionary in which we + * persist the list of databases across sessions. + */ +const DB_LIST: string = 'databaseList'; + +export interface DatabaseOptions { + displayName?: string; + ignoreSourceArchive?: boolean; +} + +interface FullDatabaseOptions extends DatabaseOptions { + ignoreSourceArchive: boolean; +} + +interface PersistedDatabaseItem { + uri: string; + options?: DatabaseOptions; +} + +/** + * The layout of the database. + */ +export enum DatabaseKind { + /** A CodeQL database */ + Database, + /** A raw QL dataset */ + RawDataset +} + +export interface DatabaseContents { + /** The layout of the database */ + kind: DatabaseKind; + /** + * The name of the database. + */ + name: string; + /** The URI of the QL dataset within the database. */ + datasetUri: vscode.Uri; + /** The URI of the source archive within the database, if one exists. */ + sourceArchiveUri?: vscode.Uri; + /** The URI of the CodeQL database scheme within the database, if exactly one exists. */ + dbSchemeUri?: vscode.Uri; +} + +/** + * An error thrown when we cannot find a valid database in a putative + * database directory. + */ +class InvalidDatabaseError extends Error { +} + + +async function findDataset(parentDirectory: string): Promise { + /* + * Look directly in the root + */ + let dbRelativePaths = await glob('db-*/', { + cwd: parentDirectory + }); + + if (dbRelativePaths.length === 0) { + /* + * Check If they are in the old location + */ + dbRelativePaths = await glob('working/db-*/', { + cwd: parentDirectory + }); + } + if (dbRelativePaths.length === 0) { + throw new InvalidDatabaseError(`'${parentDirectory}' does not contain a dataset directory.`); + } + + const dbAbsolutePath = path.join(parentDirectory, dbRelativePaths[0]); + if (dbRelativePaths.length > 1) { + showAndLogWarningMessage(`Found multiple dataset directories in database, using '${dbAbsolutePath}'.`); + } + + return vscode.Uri.file(dbAbsolutePath); +} + +async function findSourceArchive(databasePath: string, silent: boolean = false): + Promise { + + const relativePaths = ['src', 'output/src_archive'] + + for (const relativePath of relativePaths) { + const basePath = path.join(databasePath, relativePath); + const zipPath = basePath + '.zip'; + + if (await fs.pathExists(basePath)) { + return vscode.Uri.file(basePath); + } + else if (await fs.pathExists(zipPath)) { + return vscode.Uri.file(zipPath).with({ scheme: zipArchiveScheme }); + } + } + if (!silent) + showAndLogInformationMessage(`Could not find source archive for database '${databasePath}'. Assuming paths are absolute.`); + return undefined; +} + +async function resolveDatabase(databasePath: string): + Promise { + + const name = path.basename(databasePath); + + // Look for dataset and source archive. + const datasetUri = await findDataset(databasePath); + const sourceArchiveUri = await findSourceArchive(databasePath); + + return { + kind: DatabaseKind.Database, + name, + datasetUri, + sourceArchiveUri + }; + +} + +/** Gets the relative paths of all `.dbscheme` files in the given directory. */ +async function getDbSchemeFiles(dbDirectory: string): Promise { + return await glob('*.dbscheme', { cwd: dbDirectory }); +} + +async function resolveRawDataset(datasetPath: string): Promise { + if ((await getDbSchemeFiles(datasetPath)).length > 0) { + return { + kind: DatabaseKind.RawDataset, + name: path.basename(datasetPath), + datasetUri: vscode.Uri.file(datasetPath), + sourceArchiveUri: undefined + }; + } + else { + return undefined; + } +} + +async function resolveDatabaseContents(uri: vscode.Uri): Promise { + if (uri.scheme !== 'file') { + throw new Error(`Database URI scheme '${uri.scheme}' not supported; only 'file' URIs are supported.`); + } + const databasePath = uri.fsPath; + if (!await fs.pathExists(databasePath)) { + throw new InvalidDatabaseError(`Database '${databasePath}' does not exist.`); + } + + const contents = await resolveDatabase(databasePath) || await resolveRawDataset(databasePath); + + if (contents === undefined) { + throw new InvalidDatabaseError(`'${databasePath}' is not a valid database.`); + } + + // Look for a single dbscheme file within the database. + // This should be found in the dataset directory, regardless of the form of database. + const dbPath = contents.datasetUri.fsPath; + const dbSchemeFiles = await getDbSchemeFiles(dbPath); + if (dbSchemeFiles.length === 0) { + throw new InvalidDatabaseError(`Database '${databasePath}' does not contain a CodeQL dbscheme under '${dbPath}'.`); + } + else if (dbSchemeFiles.length > 1) { + throw new InvalidDatabaseError(`Database '${databasePath}' contains multiple CodeQL dbschemes under '${dbPath}'.`); + } else { + contents.dbSchemeUri = vscode.Uri.file(path.resolve(dbPath, dbSchemeFiles[0])); + } + return contents; +} + +/** An item in the list of available databases */ +export interface DatabaseItem { + /** The URI of the database */ + readonly databaseUri: vscode.Uri; + /** The name of the database to be displayed in the UI */ + readonly name: string; + /** The URI of the database's source archive, or `undefined` if no source archive is to be used. */ + readonly sourceArchive: vscode.Uri | undefined; + /** + * The contents of the database. + * Will be `undefined` if the database is invalid. Can be updated by calling `refresh()`. + */ + readonly contents: DatabaseContents | undefined; + /** If the database is invalid, describes why. */ + readonly error: Error | undefined; + /** + * Resolves the contents of the database. + * + * @remarks + * The contents include the database directory, source archive, and metadata about the database. + * If the database is invalid, `this.error` is updated with the error object that describes why + * the database is invalid. This error is also thrown. + */ + refresh(): Promise; + /** + * Resolves a filename to its URI in the source archive. + * + * @param file Filename within the source archive. May be `undefined` to return a dummy file path. + */ + resolveSourceFile(file: string | undefined): vscode.Uri; + + /** + * Holds if the database item has a `.dbinfo` file. + */ + hasDbInfo(): boolean; + + /** + * Returns `sourceLocationPrefix` of exported database. + */ + getSourceLocationPrefix(server: cli.CodeQLCliServer): Promise; + + /** + * Returns the root uri of the virtual filesystem for this database's source archive, + * as displayed in the filesystem explorer. + */ + getSourceArchiveExplorerUri(): vscode.Uri | undefined; + + /** + * Holds if `uri` belongs to this database's source archive. + */ + belongsToSourceArchiveExplorerUri(uri: vscode.Uri): boolean; +} + +class DatabaseItemImpl implements DatabaseItem { + private _error: Error | undefined = undefined; + private _contents: DatabaseContents | undefined; + /** A cache of database info */ + private _dbinfo: cli.DbInfo | undefined; + + public constructor(public readonly databaseUri: vscode.Uri, + contents: DatabaseContents | undefined, private options: FullDatabaseOptions, + private readonly onChanged: (item: DatabaseItemImpl) => void) { + + this._contents = contents; + } + + public get name(): string { + if (this.options.displayName) { + return this.options.displayName; + } + else if (this._contents) { + return this._contents.name; + } + else { + return path.basename(this.databaseUri.fsPath); + } + } + + public get sourceArchive(): vscode.Uri | undefined { + if (this.options.ignoreSourceArchive || (this._contents === undefined)) { + return undefined; + } + else { + return this._contents.sourceArchiveUri; + } + } + + public get contents(): DatabaseContents | undefined { + return this._contents; + } + + public get error(): Error | undefined { + return this._error; + } + + public async refresh(): Promise { + try { + try { + this._contents = await resolveDatabaseContents(this.databaseUri); + this._error = undefined; + } + catch (e) { + this._contents = undefined; + this._error = e; + throw e; + } + } + finally { + this.onChanged(this); + } + } + + public resolveSourceFile(file: string | undefined): vscode.Uri { + const sourceArchive = this.sourceArchive; + if (sourceArchive === undefined) { + if (file !== undefined) { + // Treat it as an absolute path. + return vscode.Uri.file(file); + } + else { + return this.databaseUri; + } + } + else { + if (file !== undefined) { + const absoluteFilePath = file.replace(':', '_'); + // Strip any leading slashes from the file path, and replace `:` with `_`. + const relativeFilePath = absoluteFilePath.replace(/^\/*/, '').replace(':', '_'); + if (sourceArchive.scheme == zipArchiveScheme) { + return encodeSourceArchiveUri({ + pathWithinSourceArchive: absoluteFilePath, + sourceArchiveZipPath: sourceArchive.fsPath, + }); + } + else { + let newPath = sourceArchive.path; + if (!newPath.endsWith('/')) { + // Ensure a trailing slash. + newPath += '/'; + } + newPath += relativeFilePath; + + return sourceArchive.with({ path: newPath }); + } + } + else { + return sourceArchive; + } + } + } + + /** + * Gets the state of this database, to be persisted in the workspace state. + */ + public getPersistedState(): PersistedDatabaseItem { + return { + uri: this.databaseUri.toString(true), + options: this.options + }; + } + + /** + * Holds if the database item refers to an exported snapshot + */ + public hasDbInfo(): boolean { + return fs.existsSync(path.join(this.databaseUri.fsPath, '.dbinfo')) + || fs.existsSync(path.join(this.databaseUri.fsPath, 'codeql-database.yml'));; + } + + /** + * Returns information about a database. + */ + private async getDbInfo(server: cli.CodeQLCliServer): Promise { + if (this._dbinfo === undefined) { + this._dbinfo = await server.resolveDatabase(this.databaseUri.fsPath); + } + return this._dbinfo; + } + + /** + * Returns `sourceLocationPrefix` of database. Requires that the database + * has a `.dbinfo` file, which is the source of the prefix. + */ + public async getSourceLocationPrefix(server: cli.CodeQLCliServer): Promise { + const dbInfo = await this.getDbInfo(server); + return dbInfo.sourceLocationPrefix; + } + + /** + * Returns the root uri of the virtual filesystem for this database's source archive. + */ + public getSourceArchiveExplorerUri(): vscode.Uri | undefined { + const sourceArchive = this.sourceArchive; + if (sourceArchive === undefined || !sourceArchive.fsPath.endsWith('.zip')) + return undefined; + return encodeSourceArchiveUri({ + pathWithinSourceArchive: '/', + sourceArchiveZipPath: sourceArchive.fsPath, + }); + } + + /** + * Holds if `uri` belongs to this database's source archive. + */ + public belongsToSourceArchiveExplorerUri(uri: vscode.Uri): boolean { + if (this.sourceArchive === undefined) + return false; + return uri.scheme === zipArchiveScheme && + decodeSourceArchiveUri(uri).sourceArchiveZipPath === this.sourceArchive.fsPath; + } +} + +/** + * A promise that resolves to an event's result value when the event + * `event` fires. If waiting for the event takes too long (by default + * >1000ms) log a warning, and resolve to undefined. + */ +function eventFired(event: vscode.Event, timeoutMs: number = 1000): Promise { + return new Promise((res, rej) => { + let timeout: NodeJS.Timeout | undefined; + let disposable: vscode.Disposable | undefined; + function dispose() { + if (timeout !== undefined) clearTimeout(timeout); + if (disposable !== undefined) disposable.dispose(); + } + disposable = event(e => { + res(e); dispose(); + }); + timeout = setTimeout(() => { + logger.log(`Waiting for event ${event} timed out after ${timeoutMs}ms`); + res(undefined); dispose(); + }, timeoutMs); + }); +} + +export class DatabaseManager extends DisposableObject { + private readonly _onDidChangeDatabaseItem = + this.push(new vscode.EventEmitter()); + readonly onDidChangeDatabaseItem = this._onDidChangeDatabaseItem.event; + + private readonly _onDidChangeCurrentDatabaseItem = + this.push(new vscode.EventEmitter()); + readonly onDidChangeCurrentDatabaseItem = this._onDidChangeCurrentDatabaseItem.event; + + private readonly _databaseItems: DatabaseItemImpl[] = []; + private _currentDatabaseItem: DatabaseItem | undefined = undefined; + + constructor(private ctx: ExtensionContext, + public config: QueryServerConfig, + public logger: Logger) { + super(); + + this.loadPersistedState(); // Let this run async. + } + + public async openDatabase(uri: vscode.Uri, options?: DatabaseOptions): + Promise { + + const contents = await resolveDatabaseContents(uri); + const realOptions = options || {}; + // Ignore the source archive for QLTest databases by default. + const isQLTestDatabase = path.extname(uri.fsPath) === '.testproj'; + const fullOptions: FullDatabaseOptions = { + ignoreSourceArchive: (realOptions.ignoreSourceArchive !== undefined) ? + realOptions.ignoreSourceArchive : isQLTestDatabase, + displayName: realOptions.displayName + }; + const databaseItem = new DatabaseItemImpl(uri, contents, fullOptions, (item) => { + this._onDidChangeDatabaseItem.fire(item); + }); + await this.addDatabaseItem(databaseItem); + await this.addDatabaseSourceArchiveFolder(databaseItem); + + return databaseItem; + } + + private async addDatabaseSourceArchiveFolder(item: DatabaseItem) { + // The folder may already be in workspace state from a previous + // session. If not, add it. + const index = this.getDatabaseWorkspaceFolderIndex(item); + if (index === -1) { + // Add that filesystem as a folder to the current workspace. + // + // It's important that we add workspace folders to the end, + // rather than beginning of the list, because the first + // workspace folder is special; if it gets updated, the entire + // extension host is restarted. (cf. + // https://github.com/microsoft/vscode/blob/e0d2ed907d1b22808c56127678fb436d604586a7/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts#L209-L214) + // + // This is undesirable, as we might be adding and removing many + // workspace folders as the user adds and removes databases. + const end = (vscode.workspace.workspaceFolders || []).length; + const uri = item.getSourceArchiveExplorerUri(); + if (uri === undefined) { + logger.log(`Couldn't obtain file explorer uri for ${item.name}`); + } + else { + logger.log(`Adding workspace folder for ${item.name} source archive at index ${end}`); + if ((vscode.workspace.workspaceFolders || []).length < 2) { + // Adding this workspace folder makes the workspace + // multi-root, which may surprise the user. Let them know + // we're doing this. + vscode.window.showInformationMessage(`Adding workspace folder for source archive of database ${item.name}.`); + } + vscode.workspace.updateWorkspaceFolders(end, 0, { + name: `[${item.name} source archive]`, + uri, + }); + // vscode api documentation says we must to wait for this event + // between multiple `updateWorkspaceFolders` calls. + await eventFired(vscode.workspace.onDidChangeWorkspaceFolders); + } + } + } + + private async createDatabaseItemFromPersistedState(state: PersistedDatabaseItem): + Promise { + + let displayName: string | undefined = undefined; + let ignoreSourceArchive = false; + if (state.options) { + if (typeof state.options.displayName === 'string') { + displayName = state.options.displayName; + } + if (typeof state.options.ignoreSourceArchive === 'boolean') { + ignoreSourceArchive = state.options.ignoreSourceArchive; + } + } + const fullOptions: FullDatabaseOptions = { + ignoreSourceArchive: ignoreSourceArchive, + displayName: displayName + }; + const item = new DatabaseItemImpl(vscode.Uri.parse(state.uri), undefined, fullOptions, + (item) => { + this._onDidChangeDatabaseItem.fire(item) + }); + await this.addDatabaseItem(item); + + return item; + } + + private async loadPersistedState(): Promise { + const currentDatabaseUri = this.ctx.workspaceState.get(CURRENT_DB); + const databases = this.ctx.workspaceState.get(DB_LIST, []); + + try { + for (const database of databases) { + const databaseItem = await this.createDatabaseItemFromPersistedState(database); + try { + await databaseItem.refresh(); + if (currentDatabaseUri === database.uri) { + this.setCurrentDatabaseItem(databaseItem, true); + } + } + catch (e) { + // When loading from persisted state, leave invalid databases in the list. They will be + // marked as invalid, and cannot be set as the current database. + } + } + } catch (e) { + // database list had an unexpected type - nothing to be done? + showAndLogErrorMessage('Database list loading failed: ${}', e.message); + } + } + + public get databaseItems(): readonly DatabaseItem[] { + return this._databaseItems; + } + + public get currentDatabaseItem(): DatabaseItem | undefined { + return this._currentDatabaseItem; + } + + public async setCurrentDatabaseItem(item: DatabaseItem | undefined, + skipRefresh: boolean = false): Promise { + + if (!skipRefresh && (item !== undefined)) { + await item.refresh(); // Will throw on invalid database. + } + if (this._currentDatabaseItem !== item) { + this._currentDatabaseItem = item; + this.updatePersistedCurrentDatabaseItem(); + this._onDidChangeCurrentDatabaseItem.fire(item); + } + } + + /** + * Returns the index of the workspace folder that corresponds to the source archive of `item` + * if there is one, and -1 otherwise. + */ + private getDatabaseWorkspaceFolderIndex(item: DatabaseItem): number { + return (vscode.workspace.workspaceFolders || []) + .findIndex(folder => item.belongsToSourceArchiveExplorerUri(folder.uri)); + } + + public findDatabaseItem(uri: vscode.Uri): DatabaseItem | undefined { + const uriString = uri.toString(true); + return this._databaseItems.find(item => item.databaseUri.toString(true) === uriString); + } + + private async addDatabaseItem(item: DatabaseItemImpl) { + this._databaseItems.push(item); + this.updatePersistedDatabaseList(); + this._onDidChangeDatabaseItem.fire(undefined); + } + + public removeDatabaseItem(item: DatabaseItem) { + if (this._currentDatabaseItem == item) + this._currentDatabaseItem = undefined; + const index = this.databaseItems.findIndex(searchItem => searchItem === item); + if (index >= 0) { + this._databaseItems.splice(index, 1); + } + this.updatePersistedDatabaseList(); + + // Delete folder from workspace, if it is still there + const folderIndex = (vscode.workspace.workspaceFolders || []).findIndex(folder => item.belongsToSourceArchiveExplorerUri(folder.uri)); + if (index >= 0) { + logger.log(`Removing workspace folder at index ${folderIndex}`); + vscode.workspace.updateWorkspaceFolders(folderIndex, 1); + } + + this._onDidChangeDatabaseItem.fire(undefined); + } + + private updatePersistedCurrentDatabaseItem(): void { + this.ctx.workspaceState.update(CURRENT_DB, this._currentDatabaseItem ? + this._currentDatabaseItem.databaseUri.toString(true) : undefined); + } + + private updatePersistedDatabaseList(): void { + this.ctx.workspaceState.update(DB_LIST, this._databaseItems.map(item => item.getPersistedState())); + } +} diff --git a/extensions/ql-vscode/src/distribution.ts b/extensions/ql-vscode/src/distribution.ts new file mode 100644 index 000000000..eb7f0ef29 --- /dev/null +++ b/extensions/ql-vscode/src/distribution.ts @@ -0,0 +1,677 @@ +import * as fetch from "node-fetch"; +import * as fs from "fs-extra"; +import * as os from "os"; +import * as path from "path"; +import * as unzipper from "unzipper"; +import * as url from "url"; +import { ExtensionContext, Event } from "vscode"; +import { DistributionConfig } from "./config"; +import { ProgressUpdate, showAndLogErrorMessage } from "./helpers"; +import { logger } from "./logging"; +import { getCodeQlCliVersion, tryParseVersionString, Version } from "./cli-version"; + +/** + * distribution.ts + * ------------ + * + * Management of CodeQL CLI binaries. + */ + +/** + * Default value for the owner name of the extension-managed distribution on GitHub. + * + * We set the default here rather than as a default config value so that this default is invoked + * upon blanking the setting. + */ +const DEFAULT_DISTRIBUTION_OWNER_NAME = "github"; + +/** + * Default value for the repository name of the extension-managed distribution on GitHub. + * + * We set the default here rather than as a default config value so that this default is invoked + * upon blanking the setting. + */ +const DEFAULT_DISTRIBUTION_REPOSITORY_NAME = "codeql-cli-binaries"; + +/** + * Version constraint for the CLI. + * + * This applies to both extension-managed and CLI distributions. + */ +export const DEFAULT_DISTRIBUTION_VERSION_CONSTRAINT: VersionConstraint = { + description: "2.0.*", + isVersionCompatible: (v: Version) => { + return v.majorVersion === 2 && v.minorVersion === 0 + } +} + +export interface DistributionProvider { + getCodeQlPathWithoutVersionCheck(): Promise, + onDidChangeDistribution?: Event +} + +export class DistributionManager implements DistributionProvider { + constructor(extensionContext: ExtensionContext, config: DistributionConfig, versionConstraint: VersionConstraint) { + this._config = config; + this._extensionSpecificDistributionManager = new ExtensionSpecificDistributionManager(extensionContext, config, versionConstraint); + this._onDidChangeDistribution = config.onDidChangeDistributionConfiguration; + this._versionConstraint = versionConstraint; + } + + /** + * Look up a CodeQL launcher binary. + */ + public async getDistribution(): Promise { + const codeQlPath = await this.getCodeQlPathWithoutVersionCheck(); + if (codeQlPath === undefined) { + return { + kind: FindDistributionResultKind.NoDistribution, + }; + } + const version = await getCodeQlCliVersion(codeQlPath, logger); + if (version !== undefined && !this._versionConstraint.isVersionCompatible(version)) { + return { + codeQlPath, + kind: FindDistributionResultKind.IncompatibleDistribution, + version, + }; + } + if (version === undefined) { + return { + codeQlPath, + kind: FindDistributionResultKind.UnknownCompatibilityDistribution, + } + } + return { + codeQlPath, + kind: FindDistributionResultKind.CompatibleDistribution, + version + }; + } + + /** + * Returns the path to a possibly-compatible CodeQL launcher binary, or undefined if a binary not be found. + */ + public async getCodeQlPathWithoutVersionCheck(): Promise { + // Check config setting, then extension specific distribution, then PATH. + if (this._config.customCodeQlPath !== undefined) { + if (!await fs.pathExists(this._config.customCodeQlPath)) { + showAndLogErrorMessage(`The CodeQL executable path is specified as "${this._config.customCodeQlPath}" ` + + "by a configuration setting, but a CodeQL executable could not be found at that path. Please check " + + "that a CodeQL executable exists at the specified path or remove the setting."); + return undefined; + } + return this._config.customCodeQlPath; + } + + const extensionSpecificCodeQlPath = await this._extensionSpecificDistributionManager.getCodeQlPathWithoutVersionCheck(); + if (extensionSpecificCodeQlPath !== undefined) { + return extensionSpecificCodeQlPath; + } + + if (process.env.PATH) { + for (const searchDirectory of process.env.PATH.split(path.delimiter)) { + const expectedLauncherPath = path.join(searchDirectory, codeQlLauncherName()); + if (await fs.pathExists(expectedLauncherPath)) { + return expectedLauncherPath; + } + } + logger.log("INFO: Could not find CodeQL on path."); + } + + return undefined; + } + + /** + * Check for updates to the extension-managed distribution. If one has not already been installed, + * this will return an update available result with the latest available release. + * + * Returns a failed promise if an unexpected error occurs during installation. + */ + public async checkForUpdatesToExtensionManagedDistribution(): Promise { + const codeQlPath = await this.getCodeQlPathWithoutVersionCheck(); + const extensionManagedCodeQlPath = await this._extensionSpecificDistributionManager.getCodeQlPathWithoutVersionCheck(); + if (codeQlPath !== undefined && codeQlPath !== extensionManagedCodeQlPath) { + // A distribution is present but it isn't managed by the extension. + return createInvalidDistributionLocationResult(); + } + return this._extensionSpecificDistributionManager.checkForUpdatesToDistribution(); + } + + /** + * Installs a release of the extension-managed distribution. + * + * Returns a failed promise if an unexpected error occurs during installation. + */ + public installExtensionManagedDistributionRelease(release: Release, + progressCallback?: (p: ProgressUpdate) => void): Promise { + return this._extensionSpecificDistributionManager.installDistributionRelease(release, progressCallback); + } + + public get onDidChangeDistribution(): Event | undefined { + return this._onDidChangeDistribution; + } + + private readonly _config: DistributionConfig; + private readonly _extensionSpecificDistributionManager: ExtensionSpecificDistributionManager; + private readonly _onDidChangeDistribution: Event | undefined; + private readonly _versionConstraint: VersionConstraint; +} + +class ExtensionSpecificDistributionManager { + constructor(extensionContext: ExtensionContext, config: DistributionConfig, versionConstraint: VersionConstraint) { + this._extensionContext = extensionContext; + this._config = config; + this._versionConstraint = versionConstraint; + } + + public async getCodeQlPathWithoutVersionCheck(): Promise { + if (this.getInstalledRelease() !== undefined) { + // An extension specific distribution has been installed. + const expectedLauncherPath = path.join(this.getDistributionRootPath(), codeQlLauncherName()); + if (await fs.pathExists(expectedLauncherPath)) { + return expectedLauncherPath; + } + logger.log(`WARNING: Expected to find a CodeQL CLI executable at ${expectedLauncherPath} but one was not found. ` + + "Will try PATH."); + try { + await this.removeDistribution(); + } catch (e) { + logger.log("WARNING: Tried to remove corrupted CodeQL CLI at " + + `${this.getDistributionStoragePath()} but encountered an error: ${e}.`); + } + } + return undefined; + } + + /** + * Check for updates to the extension-managed distribution. If one has not already been installed, + * this will return an update available result with the latest available release. + * + * Returns a failed promise if an unexpected error occurs during installation. + */ + public async checkForUpdatesToDistribution(): Promise { + const codeQlPath = await this.getCodeQlPathWithoutVersionCheck(); + const extensionSpecificRelease = this.getInstalledRelease(); + const latestRelease = await this.getLatestRelease(); + + if (extensionSpecificRelease !== undefined && codeQlPath !== undefined && latestRelease.id === extensionSpecificRelease.id) { + return createDistributionAlreadyUpToDateResult(); + } + return createUpdateAvailableResult(latestRelease); + } + + /** + * Installs a release of the extension-managed distribution. + * + * Returns a failed promise if an unexpected error occurs during installation. + */ + public async installDistributionRelease(release: Release, + progressCallback?: (p: ProgressUpdate) => void): Promise { + await this.downloadDistribution(release, progressCallback); + // Store the installed release within the global extension state. + this.storeInstalledRelease(release); + } + + private async downloadDistribution(release: Release, + progressCallback?: (p: ProgressUpdate) => void): Promise { + try { + await this.removeDistribution(); + } catch (e) { + logger.log(`Tried to clean up old version of CLI at ${this.getDistributionStoragePath()} ` + + `but encountered an error: ${e}.`); + } + + const assetStream = await this.createReleasesApiConsumer().streamBinaryContentOfAsset(release.assets[0]); + const tmpDirectory = await fs.mkdtemp(path.join(os.tmpdir(), "vscode-codeql")); + + try { + const archivePath = path.join(tmpDirectory, "distributionDownload.zip"); + const archiveFile = fs.createWriteStream(archivePath); + + const contentLength = assetStream.headers.get("content-length"); + let numBytesDownloaded = 0; + + if (progressCallback && contentLength !== null) { + const totalNumBytes = parseInt(contentLength, 10); + const bytesToDisplayMB = (numBytes: number) => `${(numBytes/(1024*1024)).toFixed(1)} MB`; + const updateProgress = () => { + progressCallback({ + step: numBytesDownloaded, + maxStep: totalNumBytes, + message: `Downloading CodeQL CLI… [${bytesToDisplayMB(numBytesDownloaded)} of ${bytesToDisplayMB(totalNumBytes)}]`, + }); + }; + + // Display the progress straight away rather than waiting for the first chunk. + updateProgress(); + + assetStream.body.on("data", data => { + numBytesDownloaded += data.length; + updateProgress(); + }); + } + + await new Promise((resolve, reject) => + assetStream.body.pipe(archiveFile) + .on("finish", resolve) + .on("error", reject) + ); + + this.bumpDistributionFolderIndex(); + + logger.log(`Extracting CodeQL CLI to ${this.getDistributionStoragePath()}`); + await extractZipArchive(archivePath, this.getDistributionStoragePath()); + } finally { + await fs.remove(tmpDirectory); + } + } + + /** + * Remove the extension-managed distribution. + * + * This should not be called for a distribution that is currently in use, as remove may fail. + */ + private async removeDistribution(): Promise { + this.storeInstalledRelease(undefined); + if (await fs.pathExists(this.getDistributionStoragePath())) { + await fs.remove(this.getDistributionStoragePath()); + } + } + + private async getLatestRelease(): Promise { + const release = await this.createReleasesApiConsumer().getLatestRelease(this._versionConstraint, this._config.includePrerelease); + if (release.assets.length !== 1) { + throw new Error("Release had an unexpected number of assets"); + } + return release; + } + + private createReleasesApiConsumer(): ReleasesApiConsumer { + const ownerName = this._config.ownerName ? this._config.ownerName : DEFAULT_DISTRIBUTION_OWNER_NAME; + const repositoryName = this._config.repositoryName ? this._config.repositoryName : DEFAULT_DISTRIBUTION_REPOSITORY_NAME; + return new ReleasesApiConsumer(ownerName, repositoryName, this._config.personalAccessToken); + } + + private bumpDistributionFolderIndex(): void { + const index = this._extensionContext.globalState.get( + ExtensionSpecificDistributionManager._currentDistributionFolderIndexStateKey, 0); + this._extensionContext.globalState.update( + ExtensionSpecificDistributionManager._currentDistributionFolderIndexStateKey, index + 1); + } + + private getDistributionStoragePath(): string { + // Use an empty string for the initial distribution for backwards compatibility. + const distributionFolderIndex = this._extensionContext.globalState.get( + ExtensionSpecificDistributionManager._currentDistributionFolderIndexStateKey, 0) || ""; + return path.join(this._extensionContext.globalStoragePath, + ExtensionSpecificDistributionManager._currentDistributionFolderBaseName + distributionFolderIndex); + } + + private getDistributionRootPath(): string { + return path.join(this.getDistributionStoragePath(), + ExtensionSpecificDistributionManager._codeQlExtractedFolderName); + } + + private getInstalledRelease(): Release | undefined { + return this._extensionContext.globalState.get(ExtensionSpecificDistributionManager._installedReleaseStateKey); + } + + private storeInstalledRelease(release: Release | undefined): void { + this._extensionContext.globalState.update(ExtensionSpecificDistributionManager._installedReleaseStateKey, release); + } + + private readonly _config: DistributionConfig; + private readonly _extensionContext: ExtensionContext; + private readonly _versionConstraint: VersionConstraint; + + private static readonly _currentDistributionFolderBaseName = "distribution"; + private static readonly _currentDistributionFolderIndexStateKey = "distributionFolderIndex"; + private static readonly _installedReleaseStateKey = "distributionRelease"; + private static readonly _codeQlExtractedFolderName = "codeql"; +} + +export class ReleasesApiConsumer { + constructor(ownerName: string, repoName: string, personalAccessToken?: string) { + // Specify version of the GitHub API + this._defaultHeaders["accept"] = "application/vnd.github.v3+json"; + + if (personalAccessToken) { + this._defaultHeaders["authorization"] = `token ${personalAccessToken}`; + } + + this._ownerName = ownerName; + this._repoName = repoName; + } + + public async getLatestRelease(versionConstraint: VersionConstraint, includePrerelease: boolean = false): Promise { + const apiPath = `/repos/${this._ownerName}/${this._repoName}/releases`; + const allReleases: GithubRelease[] = await (await this.makeApiCall(apiPath)).json(); + const compatibleReleases = allReleases.filter(release => { + if (release.prerelease && !includePrerelease) { + return false; + } + + const version = tryParseVersionString(release.tag_name); + if (version === undefined || !versionConstraint.isVersionCompatible(version)) { + return false; + } + + return true; + }); + // tryParseVersionString must succeed due to the previous filtering step + const latestRelease = compatibleReleases.sort((a, b) => { + const versionComparison = versionCompare(tryParseVersionString(b.tag_name)!, tryParseVersionString(a.tag_name)!); + if (versionComparison === 0) { + return b.created_at.localeCompare(a.created_at); + } + return versionComparison; + })[0]; + if (latestRelease === undefined) { + throw new Error("No compatible CodeQL CLI releases were found. " + + "Please check that the CodeQL extension is up to date."); + } + const assets: ReleaseAsset[] = latestRelease.assets.map(asset => { + return { + id: asset.id, + name: asset.name, + size: asset.size + }; + }); + + return { + assets, + createdAt: latestRelease.created_at, + id: latestRelease.id, + name: latestRelease.name + }; + } + + public async streamBinaryContentOfAsset(asset: ReleaseAsset): Promise { + const apiPath = `/repos/${this._ownerName}/${this._repoName}/releases/assets/${asset.id}`; + + return await this.makeApiCall(apiPath, { + "accept": "application/octet-stream" + }); + } + + protected async makeApiCall(apiPath: string, additionalHeaders: { [key: string]: string } = {}): Promise { + const response = await this.makeRawRequest(ReleasesApiConsumer._apiBase + apiPath, + Object.assign({}, this._defaultHeaders, additionalHeaders)); + + if (!response.ok) { + throw new GithubApiError(response.status, await response.text()); + } + return response; + } + + private async makeRawRequest( + requestUrl: string, + headers: { [key: string]: string }, + redirectCount: number = 0): Promise { + const response = await fetch.default(requestUrl, { + headers, + redirect: "manual" + }); + + const redirectUrl = response.headers.get("location"); + if (isRedirectStatusCode(response.status) && redirectUrl && redirectCount < ReleasesApiConsumer._maxRedirects) { + const parsedRedirectUrl = url.parse(redirectUrl); + if (parsedRedirectUrl.protocol != "https:") { + throw new Error("Encountered a non-https redirect, rejecting"); + } + if (parsedRedirectUrl.host != "api.github.com") { + // Remove authorization header if we are redirected outside of the GitHub API. + // + // This is necessary to stream release assets since AWS fails if more than one auth + // mechanism is provided. + delete headers["authorization"]; + } + return await this.makeRawRequest(redirectUrl, headers, redirectCount + 1) + } + + return response; + } + + private readonly _defaultHeaders: { [key: string]: string } = {}; + private readonly _ownerName: string; + private readonly _repoName: string; + + private static readonly _apiBase = "https://api.github.com"; + private static readonly _maxRedirects = 20; +} + +export async function extractZipArchive(archivePath: string, outPath: string): Promise { + const archive = await unzipper.Open.file(archivePath); + // This cast is necessary as the type definition for unzipper.Open.file(...).extract() is incorrect. + // It can be removed when https://github.com/DefinitelyTyped/DefinitelyTyped/pull/40240 is merged. + await (archive.extract({ + concurrency: 4, + path: outPath + }) as unknown as Promise); + // Set file permissions for extracted files + await Promise.all(archive.files.map(async file => { + // Only change file permissions if within outPath (path.join normalises the path) + const extractedPath = path.join(outPath, file.path); + if (extractedPath.indexOf(outPath) !== 0 || !(await fs.pathExists(extractedPath))) { + return Promise.resolve(); + } + return fs.chmod(extractedPath, file.externalFileAttributes >>> 16); + })); +} + +/** + * Comparison of semantic versions. + * + * Returns a positive number if a is greater than b. + * Returns 0 if a equals b. + * Returns a negative number if a is less than b. + */ +export function versionCompare(a: Version, b: Version): number { + if (a.majorVersion !== b.majorVersion) { + return a.majorVersion - b.majorVersion; + } + if (a.minorVersion !== b.minorVersion) { + return a.minorVersion - b.minorVersion; + } + if (a.patchVersion !== b.patchVersion) { + return a.patchVersion - b.patchVersion; + } + if (a.prereleaseVersion !== undefined && b.prereleaseVersion !== undefined) { + return a.prereleaseVersion.localeCompare(b.prereleaseVersion); + } + return 0; +} + +function codeQlLauncherName(): string { + return (os.platform() === "win32") ? "codeql.cmd" : "codeql"; +} + +function isRedirectStatusCode(statusCode: number): boolean { + return statusCode === 301 || statusCode === 302 || statusCode === 303 || statusCode === 307 || statusCode === 308; +} + +/* + * Types and helper functions relating to those types. + */ + +export enum FindDistributionResultKind { + CompatibleDistribution, + UnknownCompatibilityDistribution, + IncompatibleDistribution, + NoDistribution +} + +export type FindDistributionResult = CompatibleDistributionResult | UnknownCompatibilityDistributionResult | + IncompatibleDistributionResult | NoDistributionResult; + +interface CompatibleDistributionResult { + codeQlPath: string; + kind: FindDistributionResultKind.CompatibleDistribution; + version: Version +} + +interface UnknownCompatibilityDistributionResult { + codeQlPath: string; + kind: FindDistributionResultKind.UnknownCompatibilityDistribution; +} + +interface IncompatibleDistributionResult { + codeQlPath: string; + kind: FindDistributionResultKind.IncompatibleDistribution; + version: Version; +} + +interface NoDistributionResult { + kind: FindDistributionResultKind.NoDistribution; +} + +export enum DistributionUpdateCheckResultKind { + AlreadyUpToDate, + InvalidDistributionLocation, + UpdateAvailable +} + +type DistributionUpdateCheckResult = DistributionAlreadyUpToDateResult | InvalidDistributionLocationResult | + UpdateAvailableResult; + +export interface DistributionAlreadyUpToDateResult { + kind: DistributionUpdateCheckResultKind.AlreadyUpToDate; +} + +/** + * The distribution could not be installed or updated because it is not managed by the extension. + */ +export interface InvalidDistributionLocationResult { + kind: DistributionUpdateCheckResultKind.InvalidDistributionLocation; +} + +export interface UpdateAvailableResult { + kind: DistributionUpdateCheckResultKind.UpdateAvailable; + updatedRelease: Release; +} + +function createDistributionAlreadyUpToDateResult(): DistributionAlreadyUpToDateResult { + return { + kind: DistributionUpdateCheckResultKind.AlreadyUpToDate + }; +} + +function createInvalidDistributionLocationResult(): InvalidDistributionLocationResult { + return { + kind: DistributionUpdateCheckResultKind.InvalidDistributionLocation + }; +} + +function createUpdateAvailableResult(updatedRelease: Release): UpdateAvailableResult { + return { + kind: DistributionUpdateCheckResultKind.UpdateAvailable, + updatedRelease + }; +} + +/** + * A release on GitHub. + */ +export interface Release { + assets: ReleaseAsset[]; + + /** + * The creation date of the release on GitHub. + */ + createdAt: string; + + /** + * The id associated with the release on GitHub. + */ + id: number; + + /** + * The name associated with the release on GitHub. + */ + name: string; +} + +/** + * An asset corresponding to a release on GitHub. + */ +export interface ReleaseAsset { + /** + * The id associated with the asset on GitHub. + */ + id: number; + + /** + * The name associated with the asset on GitHub. + */ + name: string; + + /** + * The size of the asset in bytes. + */ + size: number; +} + + +/** + * The json returned from github for a release. + */ +export interface GithubRelease { + assets: GithubReleaseAsset[]; + + /** + * The creation date of the release on GitHub. + */ + created_at: string; + + /** + * The id associated with the release on GitHub. + */ + id: number; + + /** + * The name associated with the release on GitHub. + */ + name: string; + + /** + * Whether the release is a prerelease. + */ + prerelease: boolean; + + /** + * The tag name. This should be the version. + */ + tag_name: string; +} + +/** + * The json returned by github for an asset in a release. + */ +export interface GithubReleaseAsset { + /** + * The id associated with the asset on GitHub. + */ + id: number; + + /** + * The name associated with the asset on GitHub. + */ + name: string; + + /** + * The size of the asset in bytes. + */ + size: number; +} + +interface VersionConstraint { + description: string; + isVersionCompatible(version: Version): boolean; +} + +export class GithubApiError extends Error { + constructor(public status: number, public body: string) { + super(`API call failed with status code ${status}, body: ${body}`); + } +} diff --git a/extensions/ql-vscode/src/extension.ts b/extensions/ql-vscode/src/extension.ts new file mode 100644 index 000000000..11cd0d803 --- /dev/null +++ b/extensions/ql-vscode/src/extension.ts @@ -0,0 +1,288 @@ +import { commands, Disposable, ExtensionContext, extensions, ProgressLocation, ProgressOptions, window as Window, Uri } from 'vscode'; +import { ErrorCodes, LanguageClient, ResponseError } from 'vscode-languageclient'; +import * as archiveFilesystemProvider from './archive-filesystem-provider'; +import { DistributionConfigListener, QueryServerConfigListener } from './config'; +import { DatabaseManager } from './databases'; +import { DatabaseUI } from './databases-ui'; +import { DistributionUpdateCheckResultKind, DistributionManager, FindDistributionResult, FindDistributionResultKind, GithubApiError, DEFAULT_DISTRIBUTION_VERSION_CONSTRAINT } from './distribution'; +import * as helpers from './helpers'; +import { spawnIdeServer } from './ide-server'; +import { InterfaceManager, WebviewReveal } from './interface'; +import { ideServerLogger, logger, queryServerLogger } from './logging'; +import { compileAndRunQueryAgainstDatabase, EvaluationInfo, tmpDirDisposal, UserCancellationException } from './queries'; +import { QueryHistoryItem, QueryHistoryManager } from './query-history'; +import * as qsClient from './queryserver-client'; +import { CodeQLCliServer } from './cli'; +import { assertNever } from './helpers-pure'; + +/** + * extension.ts + * ------------ + * + * A vscode extension for CodeQL query development. + */ + +/** + * Holds when we have proceeded past the initial phase of extension activation in which + * we are trying to ensure that a valid CodeQL distribution exists, and we're actually setting + * up the bulk of the extension. + */ +let beganMainExtensionActivation = false; + +/** + * A list of vscode-registered-command disposables that contain + * temporary stub handlers for commands that exist package.json (hence + * are already connected to onscreen ui elements) but which will not + * have any useful effect if we haven't located a CodeQL distribution. + */ +const errorStubs: Disposable[] = []; + +/** + * Holds when we are installing or checking for updates to the distribution. + */ +let isInstallingOrUpdatingDistribution = false; + +/** + * If the user tries to execute vscode commands after extension activation is failed, give + * a sensible error message. + * + * @param excludedCommands List of commands for which we should not register error stubs. + */ +function registerErrorStubs(ctx: ExtensionContext, excludedCommands: string[], stubGenerator: (command: string) => () => void) { + // Remove existing stubs + errorStubs.forEach(stub => stub.dispose()); + + const extensionId = 'GitHub.vscode-codeql'; // TODO: Is there a better way of obtaining this? + const extension = extensions.getExtension(extensionId); + if (extension === undefined) + throw new Error(`Can't find extension ${extensionId}`); + + const stubbedCommands: string[] + = extension.packageJSON.contributes.commands.map((entry: { command: string }) => entry.command); + + stubbedCommands.forEach(command => { + if (excludedCommands.indexOf(command) === -1) { + errorStubs.push(commands.registerCommand(command, stubGenerator(command))); + } + }); +} + +export async function activate(ctx: ExtensionContext): Promise { + // Initialise logging, and ensure all loggers are disposed upon exit. + ctx.subscriptions.push(logger); + logger.log('Starting CodeQL extension'); + + const distributionConfigListener = new DistributionConfigListener(); + ctx.subscriptions.push(distributionConfigListener); + const distributionManager = new DistributionManager(ctx, distributionConfigListener, DEFAULT_DISTRIBUTION_VERSION_CONSTRAINT); + + const shouldUpdateOnNextActivationKey = "shouldUpdateOnNextActivation"; + + registerErrorStubs(ctx, [checkForUpdatesCommand], command => () => { + Window.showErrorMessage(`Can't execute ${command}: waiting to finish loading CodeQL CLI.`); + }); + + async function installOrUpdateDistributionWithProgressTitle(progressTitle: string, isSilentIfCannotUpdate: boolean): Promise { + const result = await distributionManager.checkForUpdatesToExtensionManagedDistribution(); + switch (result.kind) { + case DistributionUpdateCheckResultKind.AlreadyUpToDate: + if (!isSilentIfCannotUpdate) { + helpers.showAndLogInformationMessage("CodeQL CLI already up to date."); + } + break; + case DistributionUpdateCheckResultKind.InvalidDistributionLocation: + if (!isSilentIfCannotUpdate) { + helpers.showAndLogErrorMessage("CodeQL CLI is installed externally so could not be updated."); + } + break; + case DistributionUpdateCheckResultKind.UpdateAvailable: + if (beganMainExtensionActivation) { + const updateAvailableMessage = `Version "${result.updatedRelease.name}" of the CodeQL CLI is now available. ` + + "The update will be installed after Visual Studio Code restarts. Restart now to upgrade?"; + ctx.globalState.update(shouldUpdateOnNextActivationKey, true); + if (await helpers.showInformationMessageWithAction(updateAvailableMessage, "Restart and Upgrade")) { + await commands.executeCommand("workbench.action.reloadWindow"); + } + } else { + const progressOptions: ProgressOptions = { + location: ProgressLocation.Notification, + title: progressTitle, + cancellable: false, + }; + await helpers.withProgress(progressOptions, progress => + distributionManager.installExtensionManagedDistributionRelease(result.updatedRelease, progress)); + + ctx.globalState.update(shouldUpdateOnNextActivationKey, false); + helpers.showAndLogInformationMessage(`CodeQL CLI updated to version "${result.updatedRelease.name}".`); + } + break; + default: + assertNever(result); + } + } + + async function installOrUpdateDistribution(isSilentIfCannotUpdate: boolean): Promise { + if (isInstallingOrUpdatingDistribution) { + throw new Error("Already installing or updating CodeQL CLI"); + } + isInstallingOrUpdatingDistribution = true; + try { + const codeQlInstalled = await distributionManager.getCodeQlPathWithoutVersionCheck() !== undefined; + const messageText = ctx.globalState.get(shouldUpdateOnNextActivationKey) ? "Updating CodeQL CLI" : + codeQlInstalled ? "Checking for updates to CodeQL CLI" : "Installing CodeQL CLI"; + await installOrUpdateDistributionWithProgressTitle(messageText, isSilentIfCannotUpdate); + } catch (e) { + // Don't rethrow the exception, because if the config is changed, we want to be able to retry installing + // or updating the distribution. + if (e instanceof GithubApiError && (e.status == 404 || e.status == 403 || e.status === 401)) { + const errorMessageResponse = Window.showErrorMessage("Unable to download CodeQL CLI. See " + + "https://github.com/github/vscode-codeql/blob/master/extensions/ql-vscode/README.md for more details about how " + + "to obtain CodeQL CLI.", "Edit Settings"); + // We're deliberately not `await`ing this promise, just + // asynchronously letting the user follow the convenience link + // if they want to. + errorMessageResponse.then(response => { + if (response !== undefined) { + commands.executeCommand('workbench.action.openSettingsJson'); + } + }); + } else { + helpers.showAndLogErrorMessage("Unable to download CodeQL CLI. " + e); + } + } finally { + isInstallingOrUpdatingDistribution = false; + } + } + + async function getDistributionDisplayingDistributionWarnings(): Promise { + const result = await distributionManager.getDistribution(); + switch (result.kind) { + case FindDistributionResultKind.CompatibleDistribution: + logger.log(`Found compatible version of CodeQL CLI (version ${result.version.rawString})`); + break; + case FindDistributionResultKind.IncompatibleDistribution: + helpers.showAndLogWarningMessage("The current version of the CodeQL CLI is incompatible with this extension."); + break; + case FindDistributionResultKind.UnknownCompatibilityDistribution: + helpers.showAndLogWarningMessage("Compatibility with the configured CodeQL CLI could not be determined. " + + "You may experience problems using the extension."); + break; + case FindDistributionResultKind.NoDistribution: + helpers.showAndLogErrorMessage("The CodeQL CLI could not be found."); + break; + default: + assertNever(result); + } + return result; + } + + async function installOrUpdateThenTryActivate(isSilentIfCannotUpdate: boolean): Promise { + if (!isInstallingOrUpdatingDistribution) { + await installOrUpdateDistribution(isSilentIfCannotUpdate); + } + + // Display the warnings even if the extension has already activated. + const distributionResult = await getDistributionDisplayingDistributionWarnings(); + + if (!beganMainExtensionActivation && distributionResult.kind !== FindDistributionResultKind.NoDistribution) { + await activateWithInstalledDistribution(ctx, distributionManager); + } else if (distributionResult.kind === FindDistributionResultKind.NoDistribution) { + registerErrorStubs(ctx, [checkForUpdatesCommand], command => async () => { + const installActionName = "Install CodeQL CLI"; + const chosenAction = await Window.showErrorMessage(`Can't execute ${command}: missing CodeQL CLI.`, installActionName); + if (chosenAction === installActionName) { + installOrUpdateThenTryActivate(true); + } + }); + } + } + + ctx.subscriptions.push(distributionConfigListener.onDidChangeDistributionConfiguration(() => installOrUpdateThenTryActivate(true))); + ctx.subscriptions.push(commands.registerCommand(checkForUpdatesCommand, () => installOrUpdateThenTryActivate(false))); + + await installOrUpdateThenTryActivate(true); +} + +async function activateWithInstalledDistribution(ctx: ExtensionContext, distributionManager: DistributionManager) { + beganMainExtensionActivation = true; + // Remove any error stubs command handlers left over from first part + // of activation. + errorStubs.forEach(stub => stub.dispose()); + + const qlConfigurationListener = await QueryServerConfigListener.createQueryServerConfigListener(distributionManager); + ctx.subscriptions.push(qlConfigurationListener); + + ctx.subscriptions.push(queryServerLogger); + ctx.subscriptions.push(ideServerLogger); + + + const cliServer = new CodeQLCliServer(distributionManager, logger); + ctx.subscriptions.push(cliServer); + + const qs = new qsClient.QueryServerClient(qlConfigurationListener, cliServer, { + logger: queryServerLogger, + }, task => Window.withProgress({ title: 'CodeQL query server', location: ProgressLocation.Window }, task)); + ctx.subscriptions.push(qs); + await qs.startQueryServer(); + + const dbm = new DatabaseManager(ctx, qlConfigurationListener, logger); + ctx.subscriptions.push(dbm); + const databaseUI = new DatabaseUI(ctx, cliServer, dbm, qs); + ctx.subscriptions.push(databaseUI); + + const qhm = new QueryHistoryManager(ctx, async item => showResultsForInfo(item.info, WebviewReveal.Forced)); + const intm = new InterfaceManager(ctx, dbm, cliServer, queryServerLogger); + ctx.subscriptions.push(intm); + archiveFilesystemProvider.activate(ctx); + + async function showResultsForInfo(info: EvaluationInfo, forceReveal: WebviewReveal): Promise { + await intm.showResults(info, forceReveal, false); + } + + async function compileAndRunQuery(quickEval: boolean, selectedQuery: Uri | undefined) { + if (qs !== undefined) { + try { + const dbItem = await databaseUI.getDatabaseItem(); + if (dbItem === undefined) { + throw new Error('Can\'t run query without a selected database'); + } + const info = await compileAndRunQueryAgainstDatabase(cliServer, qs, dbItem, quickEval, selectedQuery); + await showResultsForInfo(info, WebviewReveal.NotForced); + qhm.push(new QueryHistoryItem(info)); + } + catch (e) { + if (e instanceof UserCancellationException) { + logger.log(e.message); + } + else if (e instanceof ResponseError && e.code == ErrorCodes.RequestCancelled) { + logger.log(e.message); + } + else if (e instanceof Error) + helpers.showAndLogErrorMessage(e.message); + else + throw e; + } + } + } + + ctx.subscriptions.push(tmpDirDisposal); + + let client = new LanguageClient('CodeQL Language Server', () => spawnIdeServer(qlConfigurationListener), { + documentSelector: [ + { language: 'ql', scheme: 'file' }, + { language: 'yaml', scheme: 'file', pattern: '**/qlpack.yml' } + ], + synchronize: { + configurationSection: 'codeQL' + }, + // Ensure that language server exceptions are logged to the same channel as its output. + outputChannel: ideServerLogger.outputChannel + }, true); + + ctx.subscriptions.push(commands.registerCommand('codeQL.runQuery', async (uri: Uri | undefined) => await compileAndRunQuery(false, uri))); + ctx.subscriptions.push(commands.registerCommand('codeQL.quickEval', async (uri: Uri | undefined) => await compileAndRunQuery(true, uri))); + + ctx.subscriptions.push(client.start()); +} + +const checkForUpdatesCommand = 'codeQL.checkForUpdatesToCLI'; diff --git a/extensions/ql-vscode/src/helpers-pure.ts b/extensions/ql-vscode/src/helpers-pure.ts new file mode 100644 index 000000000..10b2d2c07 --- /dev/null +++ b/extensions/ql-vscode/src/helpers-pure.ts @@ -0,0 +1,23 @@ +/** + * helpers-pure.ts + * ------------ + * + * Helper functions that don't depend on vscode and therefore can be used by the front-end and pure unit tests. + */ + +/** + * This error is used to indicate a runtime failure of an exhaustivity check enforced at compile time. + */ +class ExhaustivityCheckingError extends Error { + constructor(public expectedExhaustiveValue: never) { + super("Internal error: exhaustivity checking failure"); + } +} + +/** + * Used to perform compile-time exhaustivity checking on a value. This function will not be executed at runtime unless + * the type system has been subverted. + */ +export function assertNever(value: never): never { + throw new ExhaustivityCheckingError(value); +} diff --git a/extensions/ql-vscode/src/helpers.ts b/extensions/ql-vscode/src/helpers.ts new file mode 100644 index 000000000..f3c10abba --- /dev/null +++ b/extensions/ql-vscode/src/helpers.ts @@ -0,0 +1,136 @@ +import * as path from 'path'; +import { CancellationToken, ProgressOptions, window as Window, workspace } from 'vscode'; +import { logger } from './logging'; +import { EvaluationInfo } from './queries'; + +export interface ProgressUpdate { + /** + * The current step + */ + step: number; + /** + * The maximum step. This *should* be constant for a single job. + */ + maxStep: number; + /** + * The current progress message + */ + message: string; +} + +/** + * This mediates between the kind of progress callbacks we want to + * write (where we *set* current progress position and give + * `maxSteps`) and the kind vscode progress api expects us to write + * (which increment progress by a certain amount out of 100%) + */ +export function withProgress( + options: ProgressOptions, + task: ( + progress: (p: ProgressUpdate) => void, + token: CancellationToken + ) => Thenable +): Thenable { + let progressAchieved = 0; + return Window.withProgress(options, + (progress, token) => { + return task(p => { + const { message, step, maxStep } = p; + const increment = 100 * (step - progressAchieved) / maxStep; + progressAchieved = step; + progress.report({ message, increment }); + }, token); + }); +} + +/** + * Show an error message and log it to the console + * + * @param message — The message to show. + * @param items — A set of items that will be rendered as actions in the message. + * + * @return — A thenable that resolves to the selected item or undefined when being dismissed. + */ +export function showAndLogErrorMessage(message: string, ...items: string[]): Thenable { + logger.log(message); + return Window.showErrorMessage(message, ...items); +} +/** + * Show a warning message and log it to the console + * + * @param message — The message to show. + * @param items — A set of items that will be rendered as actions in the message. + * + * @return — A thenable that resolves to the selected item or undefined when being dismissed. + */ +export function showAndLogWarningMessage(message: string, ...items: string[]): Thenable { + logger.log(message); + return Window.showWarningMessage(message, ...items); +} +/** + * Show an information message and log it to the console + * + * @param message — The message to show. + * @param items — A set of items that will be rendered as actions in the message. + * + * @return — A thenable that resolves to the selected item or undefined when being dismissed. + */ +export function showAndLogInformationMessage(message: string, ...items: string[]): Thenable { + logger.log(message); + return Window.showInformationMessage(message, ...items); +} + +/** + * Opens a modal dialog for the user to make a yes/no choice. + * @param message — The message to show. + * + * @return — `true` if the user clicks 'Yes', `false` if the user clicks 'No' or cancels the dialog. + */ +export async function showBinaryChoiceDialog(message: string): Promise { + const yesItem = { title: 'Yes', isCloseAffordance: false }; + const noItem = { title: 'No', isCloseAffordance: true } + const chosenItem = await Window.showInformationMessage(message, { modal: true }, yesItem, noItem); + return chosenItem === yesItem; +} + +/** + * Show an information message with a customisable action. + * @param message — The message to show. + * @param actionMessage - The call to action message. + * + * @return — `true` if the user clicks the action, `false` if the user cancels the dialog. + */ +export async function showInformationMessageWithAction(message: string, actionMessage: string): Promise { + const actionItem = { title: actionMessage, isCloseAffordance: false }; + const chosenItem = await Window.showInformationMessage(message, actionItem); + return chosenItem === actionItem; +} + +/** Gets all active workspace folders that are on the filesystem. */ +export function getOnDiskWorkspaceFolders() { + const workspaceFolders = workspace.workspaceFolders || []; + let diskWorkspaceFolders: string[] = []; + for (const workspaceFolder of workspaceFolders) { + if (workspaceFolder.uri.scheme === "file") + diskWorkspaceFolders.push(workspaceFolder.uri.fsPath) + } + return diskWorkspaceFolders; +} + +/** + * Gets a human-readable name for an evaluated query. + * Uses metadata if it exists, and defaults to the query file name. + */ +export function getQueryName(info: EvaluationInfo) { + // Queries run through quick evaluation are not usually the entire query file. + // Label them differently and include the line numbers. + if (info.query.quickEvalPosition !== undefined) { + const { line, endLine, fileName } = info.query.quickEvalPosition; + const lineInfo = line === endLine ? `${line}` : `${line}-${endLine}`; + return `Quick evaluation of ${path.basename(fileName)}:${lineInfo}`; + } else if (info.query.metadata && info.query.metadata.name) { + return info.query.metadata.name; + } else { + return path.basename(info.query.program.queryPath); + } +} diff --git a/extensions/ql-vscode/src/ide-server.ts b/extensions/ql-vscode/src/ide-server.ts new file mode 100644 index 000000000..e2b83a0e6 --- /dev/null +++ b/extensions/ql-vscode/src/ide-server.ts @@ -0,0 +1,26 @@ +import { ProgressLocation, window } from 'vscode'; +import { StreamInfo } from 'vscode-languageclient'; +import * as cli from './cli'; +import { QueryServerConfig } from './config'; +import { ideServerLogger } from './logging'; + +/** + * Managing the language server for CodeQL. + */ + +/** Starts a new CodeQL language server process, sending progress messages to the status bar. */ +export async function spawnIdeServer(config: QueryServerConfig): Promise { + return window.withProgress({ title: 'CodeQL language server', location: ProgressLocation.Window }, async (progressReporter, _) => { + const child = cli.spawnServer( + config.codeQlPath, + 'CodeQL language server', + ['execute', 'language-server'], + ['--check-errors', 'ON_CHANGE'], + ideServerLogger, + data => ideServerLogger.logWithoutTrailingNewline(data.toString()), + data => ideServerLogger.logWithoutTrailingNewline(data.toString()), + progressReporter + ); + return { writer: child.stdin!, reader: child.stdout! }; + }); +} diff --git a/extensions/ql-vscode/src/interface-types.ts b/extensions/ql-vscode/src/interface-types.ts new file mode 100644 index 000000000..ded44c99c --- /dev/null +++ b/extensions/ql-vscode/src/interface-types.ts @@ -0,0 +1,103 @@ +import * as sarif from 'sarif'; +import { ResolvableLocationValue } from 'semmle-bqrs'; + +/** + * Only ever show this many results per run in interpreted results. + */ +export const INTERPRETED_RESULTS_PER_RUN_LIMIT = 100; + +/** + * Only ever show this many rows in a raw result table. + */ +export const RAW_RESULTS_LIMIT = 10000; + +export interface DatabaseInfo { + name: string; + databaseUri: string; +} + +export interface PreviousExecution { + queryName: string; + time: string; + databaseName: string; + durationSeconds: number; +} + +export interface Interpretation { + sourceLocationPrefix: string; + numTruncatedResults: number; + sarif: sarif.Log; +} + +export interface ResultsInfo { + resultsPath: string; + interpretedResultsPath: string; +} + +export interface SortedResultSetInfo { + resultsPath: string; + sortState: SortState; +} + +export type SortedResultsMap = { [resultSet: string]: SortedResultSetInfo }; + +/** + * A message to indicate that the results are being updated. + * + * As a result of receiving this message, listeners might want to display a loading indicator. + */ +export interface ResultsUpdatingMsg { + t: 'resultsUpdating'; +} + +export interface SetStateMsg { + t: 'setState'; + resultsPath: string; + sortedResultsMap: SortedResultsMap; + interpretation: undefined | Interpretation; + database: DatabaseInfo; + kind?: string; + /** + * Whether to keep displaying the old results while rendering the new results. + * + * This is useful to prevent properties like scroll state being lost when rendering the sorted results after sorting a column. + */ + shouldKeepOldResultsWhileRendering: boolean; +}; + +export type IntoResultsViewMsg = ResultsUpdatingMsg | SetStateMsg; + +export type FromResultsViewMsg = ViewSourceFileMsg | ToggleDiagnostics | ChangeSortMsg | ResultViewLoaded; + +interface ViewSourceFileMsg { + t: 'viewSourceFile'; + loc: ResolvableLocationValue; + databaseUri: string; +}; + +interface ToggleDiagnostics { + t: 'toggleDiagnostics'; + databaseUri: string; + resultsPath: string; + visible: boolean; + kind?: string; +}; + +interface ResultViewLoaded { + t: 'resultViewLoaded'; +}; + +export enum SortDirection { + asc, desc +} + +export interface SortState { + columnIndex: number; + direction: SortDirection; +} + +interface ChangeSortMsg { + t: 'changeSort'; + resultSetName: string; + sortState?: SortState; +} diff --git a/extensions/ql-vscode/src/interface.ts b/extensions/ql-vscode/src/interface.ts new file mode 100644 index 000000000..da8dafded --- /dev/null +++ b/extensions/ql-vscode/src/interface.ts @@ -0,0 +1,477 @@ +import * as crypto from 'crypto'; +import * as path from 'path'; +import * as bqrs from 'semmle-bqrs'; +import { CustomResultSets, FivePartLocation, LocationStyle, LocationValue, PathProblemQueryResults, ProblemQueryResults, ResolvableLocationValue, tryGetResolvableLocation, WholeFileLocation } from 'semmle-bqrs'; +import { FileReader } from 'semmle-io-node'; +import { DisposableObject } from 'semmle-vscode-utils'; +import * as vscode from 'vscode'; +import { Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, languages, Location, Position, Range, Uri, window as Window, workspace } from 'vscode'; +import { CodeQLCliServer } from './cli'; +import { DatabaseItem, DatabaseManager } from './databases'; +import * as helpers from './helpers'; +import { showAndLogErrorMessage } from './helpers'; +import { assertNever } from './helpers-pure'; +import { FromResultsViewMsg, Interpretation, IntoResultsViewMsg, ResultsInfo, SortedResultSetInfo, SortedResultsMap, INTERPRETED_RESULTS_PER_RUN_LIMIT } from './interface-types'; +import { Logger } from './logging'; +import * as messages from './messages'; +import { EvaluationInfo, interpretResults, QueryInfo, tmpDir } from './queries'; + +/** + * interface.ts + * ------------ + * + * Displaying query results and linking back to source files when the + * webview asks us to. + */ + +/** Gets a nonce string created with 128 bits of entropy. */ +function getNonce(): string { + return crypto.randomBytes(16).toString('base64'); +} + +/** + * Whether to force webview to reveal + */ +export enum WebviewReveal { + Forced, + NotForced, +} + +/** + * Returns HTML to populate the given webview. + * Uses a content security policy that only loads the given script. + */ +function getHtmlForWebview(webview: vscode.Webview, scriptUriOnDisk: vscode.Uri, stylesheetUriOnDisk: vscode.Uri) { + // Convert the on-disk URIs into webview URIs. + const scriptWebviewUri = webview.asWebviewUri(scriptUriOnDisk); + const stylesheetWebviewUri = webview.asWebviewUri(stylesheetUriOnDisk); + // Use a nonce in the content security policy to uniquely identify the above resources. + const nonce = getNonce(); + /* + * Content security policy: + * default-src: allow nothing by default. + * script-src: allow only the given script, using the nonce. + * style-src: allow only the given stylesheet, using the nonce. + * connect-src: only allow fetch calls to webview resource URIs + * (this is used to load BQRS result files). + */ + const html = ` + + + + + + +

+ + +`; + webview.html = html; +} + +/** Converts a filesystem URI into a webview URI string that the given panel can use to read the file. */ +export function fileUriToWebviewUri(panel: vscode.WebviewPanel, fileUriOnDisk: Uri): string { + return encodeURI(panel.webview.asWebviewUri(fileUriOnDisk).toString(true)); +} + +/** Converts a URI string received from a webview into a local filesystem URI for the same resource. */ +export function webviewUriToFileUri(webviewUri: string): Uri { + // Webview URIs used the vscode-resource scheme. The filesystem path of the resource can be obtained from the path component of the webview URI. + const path = Uri.parse(webviewUri).path; + // For this path to be interpreted on the filesystem, we need to parse it as a filesystem URI for the current platform. + return Uri.file(path); +} + +export class InterfaceManager extends DisposableObject { + private _displayedEvaluationInfo?: EvaluationInfo; + private _panel: vscode.WebviewPanel | undefined; + private _panelLoaded = false; + private _panelLoadedCallBacks: (() => void)[] = []; + + private readonly _diagnosticCollection = languages.createDiagnosticCollection(`codeql-query-results`); + + constructor(public ctx: vscode.ExtensionContext, private databaseManager: DatabaseManager, + public cliServer: CodeQLCliServer, public logger: Logger) { + + super(); + this.push(this._diagnosticCollection); + } + + // Returns the webview panel, creating it if it doesn't already + // exist. + getPanel(): vscode.WebviewPanel { + if (this._panel == undefined) { + const { ctx } = this; + const panel = this._panel = Window.createWebviewPanel( + 'resultsView', // internal name + 'CodeQL Query Results', // user-visible name + { viewColumn: vscode.ViewColumn.Beside, preserveFocus: true }, + { + enableScripts: true, + enableFindWidget: true, + retainContextWhenHidden: true, + localResourceRoots: [ + vscode.Uri.file(tmpDir.name), + vscode.Uri.file(path.join(this.ctx.extensionPath, 'out')) + ] + } + ); + this._panel.onDidDispose(() => { this._panel = undefined; }, null, ctx.subscriptions); + const scriptPathOnDisk = vscode.Uri + .file(ctx.asAbsolutePath('out/resultsView.js')); + const stylesheetPathOnDisk = vscode.Uri + .file(ctx.asAbsolutePath('out/resultsView.css')); + getHtmlForWebview(panel.webview, scriptPathOnDisk, stylesheetPathOnDisk); + panel.webview.onDidReceiveMessage(async (e) => this.handleMsgFromView(e), undefined, ctx.subscriptions); + } + return this._panel; + } + + private async handleMsgFromView(msg: FromResultsViewMsg): Promise { + switch (msg.t) { + case 'viewSourceFile': { + const databaseItem = this.databaseManager.findDatabaseItem(Uri.parse(msg.databaseUri)); + if (databaseItem !== undefined) { + try { + await showLocation(msg.loc, databaseItem); + } + catch (e) { + if (e instanceof Error) { + if (e.message.match(/File not found/)) { + vscode.window.showErrorMessage(`Original file of this result is not in the database's source archive.`); + } + else { + this.logger.log(`Unable to handleMsgFromView: ${e.message}`); + } + } + else { + this.logger.log(`Unable to handleMsgFromView: ${e}`); + } + } + } + break; + } + case 'toggleDiagnostics': { + if (msg.visible) { + const databaseItem = this.databaseManager.findDatabaseItem(Uri.parse(msg.databaseUri)); + if (databaseItem !== undefined) { + await this.showResultsAsDiagnostics(msg.resultsPath, msg.kind, databaseItem); + } + } else { + // TODO: Only clear diagnostics on the same database. + this._diagnosticCollection.clear(); + } + break; + } + case "resultViewLoaded": + this._panelLoaded = true; + this._panelLoadedCallBacks.forEach(cb => cb()); + this._panelLoadedCallBacks = []; + break; + case 'changeSort': { + if (this._displayedEvaluationInfo === undefined) { + showAndLogErrorMessage("Failed to sort results since evaluation info was unknown."); + break; + } + // Notify the webview that it should expect new results. + await this.postMessage({ t: 'resultsUpdating' }); + await this._displayedEvaluationInfo.query.updateSortState(this.cliServer, msg.resultSetName, msg.sortState); + await this.showResults(this._displayedEvaluationInfo, WebviewReveal.NotForced, true); + break; + } + default: + assertNever(msg); + } + } + + postMessage(msg: IntoResultsViewMsg): Thenable { + return this.getPanel().webview.postMessage(msg); + } + + private waitForPanelLoaded(): Promise { + return new Promise((resolve, reject) => { + if (this._panelLoaded) { + resolve(); + } else { + this._panelLoadedCallBacks.push(resolve) + } + }) + } + + /** + * Show query results in webview panel. + * @param info Evaluation info for the executed query. + * @param shouldKeepOldResultsWhileRendering Should keep old results while rendering. + * @param forceReveal Force the webview panel to be visible and + * Appropriate when the user has just performed an explicit + * UI interaction requesting results, e.g. clicking on a query + * history entry. + */ + public async showResults(info: EvaluationInfo, forceReveal: WebviewReveal, shouldKeepOldResultsWhileRendering: boolean = false): Promise { + if (info.result.resultType !== messages.QueryResultType.SUCCESS) { + return; + } + + const interpretation = await this.interpretResultsInfo(info.query, info.query.resultsInfo); + + const sortedResultsMap: SortedResultsMap = {}; + info.query.sortedResultsInfo.forEach((v, k) => + sortedResultsMap[k] = this.convertPathPropertiesToWebviewUris(v)); + + this._displayedEvaluationInfo = info; + + const panel = this.getPanel(); + await this.waitForPanelLoaded(); + if (forceReveal === WebviewReveal.Forced) { + panel.reveal(undefined, true); + } + else if (!panel.visible) { + // The results panel exists, (`.getPanel()` guarantees it) but + // is not visible; it's in a not-currently-viewed tab. Show a + // more asynchronous message to not so abruptly interrupt + // user's workflow by immediately revealing the panel. + const showButton = 'View Results'; + const queryName = helpers.getQueryName(info); + let queryNameForMessage: string; + if (queryName.length > 0) { + // lower case the first character + queryNameForMessage = queryName.charAt(0).toLowerCase() + queryName.substring(1); + } else { + queryNameForMessage = 'query'; + } + const resultPromise = vscode.window.showInformationMessage( + `Finished running ${queryNameForMessage}.`, + showButton + ); + // Address this click asynchronously so we still update the + // query history immediately. + resultPromise.then(result => { + if (result === showButton) { + panel.reveal(); + } + }); + } + + await this.postMessage({ + t: 'setState', + interpretation, + resultsPath: this.convertPathToWebviewUri(info.query.resultsInfo.resultsPath), + sortedResultsMap, + database: info.database, + shouldKeepOldResultsWhileRendering, + kind: info.query.metadata ? info.query.metadata.kind : undefined + }); + } + + private async interpretResultsInfo(query: QueryInfo, resultsInfo: ResultsInfo): Promise { + let interpretation: Interpretation | undefined = undefined; + if (query.hasInterpretedResults() + && query.quickEvalPosition === undefined // never do results interpretation if quickEval + ) { + try { + const sourceLocationPrefix = await query.dbItem.getSourceLocationPrefix(this.cliServer); + const sourceArchiveUri = query.dbItem.sourceArchive; + const sourceInfo = sourceArchiveUri === undefined ? + undefined : + { sourceArchive: sourceArchiveUri.fsPath, sourceLocationPrefix }; + const sarif = await interpretResults(this.cliServer, query, resultsInfo, sourceInfo); + // For performance reasons, limit the number of results we try + // to serialize and send to the webview. TODO: possibly also + // limit number of paths per result, number of steps per path, + // or throw an error if we are in aggregate trying to send + // massively too much data, as it can make the extension + // unresponsive. + let numTruncatedResults = 0; + sarif.runs.forEach(run => { + if (run.results !== undefined) { + if (run.results.length > INTERPRETED_RESULTS_PER_RUN_LIMIT) { + numTruncatedResults += run.results.length - INTERPRETED_RESULTS_PER_RUN_LIMIT; + run.results = run.results.slice(0, INTERPRETED_RESULTS_PER_RUN_LIMIT); + } + } + }); + interpretation = { sarif, sourceLocationPrefix, numTruncatedResults }; + } + catch (e) { + // If interpretation fails, accept the error and continue + // trying to render uninterpreted results anyway. + this.logger.log(`Exception during results interpretation: ${e.message}. Will show raw results instead.`); + } + } + + return interpretation; + } + + private async showResultsAsDiagnostics(resultsPath: string, kind: string | undefined, + database: DatabaseItem) { + + // URIs from the webview have the vscode-resource scheme, so convert into a filesystem URI first. + const resultsPathOnDisk = webviewUriToFileUri(resultsPath).fsPath; + const fileReader = await FileReader.open(resultsPathOnDisk); + try { + const resultSets = await bqrs.open(fileReader); + try { + switch (kind || 'problem') { + case 'problem': { + const customResults = bqrs.createCustomResultSets(resultSets, ProblemQueryResults); + await this.showProblemResultsAsDiagnostics(customResults, database); + } + break; + + case 'path-problem': { + const customResults = bqrs.createCustomResultSets(resultSets, PathProblemQueryResults); + await this.showProblemResultsAsDiagnostics(customResults, database); + } + break; + + default: + throw new Error(`Unrecognized query kind '${kind}'.`); + } + } + catch (e) { + const msg = e instanceof Error ? e.message : e.toString(); + this.logger.log(`Exception while computing problem results as diagnostics: ${msg}`); + this._diagnosticCollection.clear(); + } + } + finally { + fileReader.dispose(); + } + } + + private async showProblemResultsAsDiagnostics(results: CustomResultSets, + databaseItem: DatabaseItem): Promise { + + const diagnostics: [Uri, ReadonlyArray][] = []; + for await (const problemRow of results.problems.readTuples()) { + const codeLocation = resolveLocation(problemRow.element.location, databaseItem); + let message: string; + const references = problemRow.references; + if (references) { + let referenceIndex = 0; + message = problemRow.message.replace(/\$\@/g, sub => { + if (referenceIndex < references.length) { + const replacement = references[referenceIndex].text; + referenceIndex++; + return replacement; + } + else { + return sub; + } + }); + } + else { + message = problemRow.message; + } + const diagnostic = new Diagnostic(codeLocation.range, message, DiagnosticSeverity.Warning); + if (problemRow.references) { + const relatedInformation: DiagnosticRelatedInformation[] = []; + for (const reference of problemRow.references) { + const referenceLocation = tryResolveLocation(reference.element.location, databaseItem); + if (referenceLocation) { + const related = new DiagnosticRelatedInformation(referenceLocation, + reference.text); + relatedInformation.push(related); + } + } + diagnostic.relatedInformation = relatedInformation; + } + diagnostics.push([ + codeLocation.uri, + [diagnostic] + ]); + } + + this._diagnosticCollection.set(diagnostics); + } + + private convertPathToWebviewUri(path: string): string { + return fileUriToWebviewUri(this.getPanel(), Uri.file(path)); + } + + private convertPathPropertiesToWebviewUris(info: SortedResultSetInfo): SortedResultSetInfo { + return { + resultsPath: this.convertPathToWebviewUri(info.resultsPath), + sortState: info.sortState + }; + } +} + +async function showLocation(loc: ResolvableLocationValue, databaseItem: DatabaseItem): Promise { + const resolvedLocation = tryResolveLocation(loc, databaseItem); + if (resolvedLocation) { + const doc = await workspace.openTextDocument(resolvedLocation.uri); + const editor = await Window.showTextDocument(doc, vscode.ViewColumn.One); + const sel = new vscode.Selection(resolvedLocation.range.start, resolvedLocation.range.end); + editor.selection = sel; + editor.revealRange(sel, vscode.TextEditorRevealType.InCenter); + } +} + +/** + * Resolves the specified CodeQL location to a URI into the source archive. + * @param loc CodeQL location to resolve. Must have a non-empty value for `loc.file`. + * @param databaseItem Database in which to resolve the file location. + */ +function resolveFivePartLocation(loc: FivePartLocation, databaseItem: DatabaseItem): Location { + // `Range` is a half-open interval, and is zero-based. CodeQL locations are closed intervals, and + // are one-based. Adjust accordingly. + const range = new Range(Math.max(0, loc.lineStart - 1), + Math.max(0, loc.colStart - 1), + Math.max(0, loc.lineEnd - 1), + Math.max(0, loc.colEnd)); + + return new Location(databaseItem.resolveSourceFile(loc.file), range); +} + +/** + * Resolves the specified CodeQL filesystem resource location to a URI into the source archive. + * @param loc CodeQL location to resolve, corresponding to an entire filesystem resource. Must have a non-empty value for `loc.file`. + * @param databaseItem Database in which to resolve the filesystem resource location. + */ +function resolveWholeFileLocation(loc: WholeFileLocation, databaseItem: DatabaseItem): Location { + // A location corresponding to the start of the file. + const range = new Range(0, 0, 0, 0); + return new Location(databaseItem.resolveSourceFile(loc.file), range); +} + +/** + * Resolve the specified CodeQL location to a URI into the source archive. + * @param loc CodeQL location to resolve + * @param databaseItem Database in which to resolve the file location. + */ +function resolveLocation(loc: LocationValue | undefined, databaseItem: DatabaseItem): Location { + const resolvedLocation = tryResolveLocation(loc, databaseItem); + if (resolvedLocation) { + return resolvedLocation; + } + else { + // Return a fake position in the source archive directory itself. + return new Location(databaseItem.resolveSourceFile(undefined), new Position(0, 0)); + } +} + +/** + * Try to resolve the specified CodeQL location to a URI into the source archive. If no exact location + * can be resolved, returns `undefined`. + * @param loc CodeQL location to resolve + * @param databaseItem Database in which to resolve the file location. + */ +function tryResolveLocation(loc: LocationValue | undefined, + databaseItem: DatabaseItem): Location | undefined { + const resolvableLoc = tryGetResolvableLocation(loc); + if (resolvableLoc === undefined) { + return undefined; + } + switch (resolvableLoc.t) { + case LocationStyle.FivePart: + return resolveFivePartLocation(resolvableLoc, databaseItem); + case LocationStyle.WholeFile: + return resolveWholeFileLocation(resolvableLoc, databaseItem); + default: + return undefined; + } +} diff --git a/extensions/ql-vscode/src/logging.ts b/extensions/ql-vscode/src/logging.ts new file mode 100644 index 000000000..142a501f9 --- /dev/null +++ b/extensions/ql-vscode/src/logging.ts @@ -0,0 +1,40 @@ +import { window as Window, OutputChannel, Progress } from 'vscode'; +import { DisposableObject } from 'semmle-vscode-utils'; + +export interface Logger { + /** Writes the given log message, followed by a newline. */ + log(message: string): void; + /** Writes the given log message, not followed by a newline. */ + logWithoutTrailingNewline(message: string): void; +} + +export type ProgressReporter = Progress<{ message: string }>; + +/** A logger that writes messages to an output channel in the Output tab. */ +export class OutputChannelLogger extends DisposableObject implements Logger { + outputChannel: OutputChannel; + + constructor(title: string) { + super(); + this.outputChannel = Window.createOutputChannel(title); + this.push(this.outputChannel); + } + + log(message: string) { + this.outputChannel.appendLine(message); + } + + logWithoutTrailingNewline(message: string) { + this.outputChannel.append(message); + } + +} + +/** The global logger for the extension. */ +export const logger = new OutputChannelLogger('CodeQL Extension Log'); + +/** The logger for messages from the query server. */ +export const queryServerLogger = new OutputChannelLogger('CodeQL Query Server'); + +/** The logger for messages from the language server. */ +export const ideServerLogger = new OutputChannelLogger('CodeQL Language Server'); diff --git a/extensions/ql-vscode/src/messages.ts b/extensions/ql-vscode/src/messages.ts new file mode 100644 index 000000000..bc12c8cf7 --- /dev/null +++ b/extensions/ql-vscode/src/messages.ts @@ -0,0 +1,918 @@ +/** + * Types for messages exchanged during jsonrpc communication with the + * the CodeQL query server. + */ + +import * as rpc from 'vscode-jsonrpc'; + +/** + * A position within a QL file. + */ +export interface Position { + /** + * The one-based index of the start line + */ + line: number; + /** + * The one-based offset of the start column within + * the start line in UTF-16 code-units + */ + column: number; + /** + * The one-based index of the end line line + */ + endLine: number; + + /** + * The one-based offset of the end column within + * the end line in UTF-16 code-units + */ + endColumn: number; + /** + * The path of the file. + * If the file name is "Compiler Generated" the + * the position is not a real position but + * arises from compiler generated code. + */ + fileName: string; +} + +/** + * A query that should be checked for any errors or warnings + */ +export interface CheckQueryParams { + /** + * The options for compilation, if missing then the default options. + */ + compilationOptions?: CompilationOptions; + /** + * The ql program to check. + */ + queryToCheck: QlProgram; + /** + * The way of compiling a query + */ + target: CompilationTarget; +} + +/** + * A query that should compiled into a qlo + */ +export interface CompileQueryParams { + /** + * The options for compilation, if missing then the default options. + */ + compilationOptions?: CompilationOptions; + /** + * The options for compilation that do not affect the result. + */ + extraOptions?: ExtraOptions; + /** + * The ql program to check. + */ + queryToCheck: QlProgram; + /** + * The way of compiling a query + */ + target: CompilationTarget; + /** + * The path to write the qlo at. + */ + resultPath?: string; +} + +/** + * A dil (datalog intermediate language) query that should compiled into a qlo + */ +export interface CompileDilParams { + /** + * The options for compilation, if missing then the default options. + */ + compilationOptions?: DilCompilationOptions; + /** + * The options for compilation that do not affect the result. + */ + extraOptions?: ExtraOptions; + /** + * The dil query to compile + */ + dilQuery?: DILQuery; + /** + * The path to write the qlo at. + */ + resultPath?: string; +} + + +/** + * The options for QL compilation. + */ +export interface CompilationOptions { + /** + * Whether to ensure that elements that do not have a location or URL + * get a default location. + */ + computeNoLocationUrls: boolean; + /** + * Whether to fail if any warnings occur in the ql code. + */ + failOnWarnings: boolean; + /** + * Whether to compile as fast as possible, at the expense + * of optimization. + */ + fastCompilation: boolean; + /** + * Whether to include dil within qlos. + */ + includeDilInQlo: boolean; + /** + * Whether to only do the initial program checks on the subset of the program that + * is used. + */ + localChecking: boolean; + /** + * Whether to disable urls in the results. + */ + noComputeGetUrl: boolean; + /** + * Whether to disable toString values in the results. + */ + noComputeToString: boolean; +} + +/** + * Compilation options that do not affect the result of + * query compilation + */ +export interface ExtraOptions { + /** + * The uris of any additional compilation caches + * TODO: Document cache uri format + */ + extraCompilationCache?: string; + /** + * The compilation timeout in seconds. If it is + * zero then there is no timeout. + */ + timeoutSecs: number; +} + + +/** + * The DIL compilation options + */ +export interface DilCompilationOptions { + /** + * Whether to compile as fast as possible, at the expense + * of optimization. + */ + fastCompilation: boolean; + /** + * Whether to include dil within qlos. + */ + includeDilInQlo: boolean; +} + +/** + * A full ql program + */ +export interface QlProgram { + /** + * The path to the dbscheme + */ + dbschemePath: string; + /** + *The ql library search path + */ + libraryPath: string[]; + /** + * The path to the query + */ + queryPath: string; + /** + * If set then the contents of the source files. + * Otherwise they will be searched for on disk. + */ + sourceContents?: QlFileSet; +} + +/** + * A representation of files in query with all imports + * pre-resolved. + */ +export interface QlFileSet { + /** + * The files imported by the given file + */ + imports: { [key: string]: string[]; }; + /** + * An id of each file + */ + nodeNumbering: { [key: string]: number; }; + /** + * The code for each file + */ + qlCode: { [key: string]: string; }; + /** + * The resolution of an import in each directory. + */ + resolvedDirImports: { [key: string]: { [key: string]: string; }; }; +} + +/** + * An uncompiled dil query + */ +export interface DILQuery { + /** + * The path to the dbscheme + */ + dbschemePath: string; + /** + * The path to the dil file + */ + dilPath: string; + /** + * The dil source + */ + dilSource: string; +} + +/** + * The way of compiling the query, as a normal query + * or a subset of it. Note that precisely one of the two options should be set. + */ +export interface CompilationTarget { + /** + * Compile as a normal query + */ + query?: {}; + /** + * Compile as a quick evaluation + */ + quickEval?: QuickEvalOptions; +} + +/** + * Options for quick evaluation + */ +export interface QuickEvalOptions { + quickEvalPos?: Position; +} + +/** + * The result of checking a query. + */ +export interface CheckQueryResult { + /** + * Whether the query came from a compilation cache + */ + fromCache: boolean; + /** + * The errors or warnings that occurred during compilation + */ + messages: CompilationMessage[]; + /** + * The types of the query predicates of the query + */ + resultPatterns: ResultPattern[]; +} + + +/** + * A compilation message (either an error or a warning) + */ +export interface CompilationMessage { + /** + * The text of the message + */ + message: string; + /** + * The source position associated with the message + */ + position: Position; + /** + * The severity of the message + */ + severity: number; +} +/** + * Severity of different messages + */ +export namespace Severity { + /** + * The message is a compilation error. + */ + export const ERROR = 0; + /** + * The message is a compilation warning. + */ + export const WARNING = 1; +} + +/** + * The type of a query predicate + */ +export interface ResultPattern { + /** + * The types of the columns of the query predicate + */ + columns: ResultColumn[]; + /** + * The name of the query predicate. + * #select" is used as the name of a select clause. + */ + name: string; +} + +/** + * The name and type of a single column + */ +export interface ResultColumn { + /** + * The kind of the column. See `ResultColumnKind` + * for the current possible meanings + */ + kind: number; + /** + * The name of the column. + * This may be compiler generated for complex select expressions. + */ + name: string; +} + +/** + * The kind of a result column. + */ +export namespace ResultColumnKind { + /** + * A column of type `float` + */ + export const FLOAT = 0; + /** + * A column of type `int` + */ + export const INTEGER = 1; + /** + * A column of type `string` + */ + export const STRING = 2; + /** + * A column of type `boolean` + */ + export const BOOLEAN = 3; + /** + * A column of type `date` + */ + export const DATE = 4; + /** + * A column of a non-primitive type + */ + export const ENTITY = 5; +} + +/** + * Parameters for compiling an upgrade. + */ +export interface CompileUpgradeParams { + /** + * The parameters for how to compile the upgrades + */ + upgrade: UpgradeParams; + /** + * A directory to store parts of the compiled upgrade + */ + upgradeTempDir: string; +} + +/** + * Parameters describing an upgrade + */ +export interface UpgradeParams { + /** + * The location of non built-in upgrades + */ + additionalUpgrades: string[]; + /** + * The path to the current dbscheme to start the upgrade + */ + fromDbscheme: string; + /** + * The path to the target dbscheme to try an upgrade to + */ + toDbscheme: string; +} + +/** + * The result of checking an upgrade + */ +export interface CheckUpgradeResult { + /** + * A description of the steps to take to upgrade this dataset. + * Note that the list may be partial. + */ + checkedUpgrades?: UpgradesDescription; + /** + * Any errors that occurred when checking the scripts. + */ + upgradeError?: string; +} + + +/** + * The result of compiling an upgrade + */ +export interface CompileUpgradeResult { + /** + * The compiled upgrade. + */ + compiledUpgrades?: CompiledUpgrades; + /** + * Any errors that occurred when checking the scripts. + */ + error?: string; +} +/** + * A description of a upgrade process + */ +export interface UpgradesDescription { + /** + * The initial sha of the dbscheme to upgrade from + */ + initialSha: string; + /** + * A list of description of the steps in the upgrade process. + * Note that this may only upgrade partially + */ + scripts: UpgradeDescription[]; + /** + * The sha of the target dataset. + */ + targetSha: string; +} + +/** + * The description of a single step + */ +export interface UpgradeDescription { + /** + * The compatibility of the upgrade + */ + compatibility: string; + /** + * A description of the upgrade + */ + description: string; + /** + * The dbscheme sha after this upgrade has run. + */ + newSha: string; +} + +/** + * A compiled upgrade. + */ +export interface CompiledUpgrades { + /** + * The initial sha of the dbscheme to upgrade from + */ + initialSha: string; + /** + * The path to the new dataset statistics + */ + newStatsPath: string; + /** + * The steps in the upgrade path + */ + scripts: CompiledUpgradeScript[]; + /** + * The sha of the target dataset. + */ + targetSha: string; +} + +/** + * A compiled step to upgrade the dataset. + */ +export interface CompiledUpgradeScript { + /** + * A description of the spec + */ + description: UpgradeDescription; + /** + * The path to the dbscheme that this upgrade step + * upgrades to. + */ + newDbschemePath: string; + /** + * The actions required to run this step. + */ + specs: UpgradeAction[]; +} + +/** + * An action used to upgrade a query. + * Only one of the options should be set + */ +export interface UpgradeAction { + deleted?: DeleteSpec; + runQuery?: QloSpec; +} + +/** + * Delete a relation + */ +export interface DeleteSpec { + /** + * The name of the relation to delete + */ + relationToDelete: string; +} + +/** + * Run a qlo to provide a relation + */ +export interface QloSpec { + /** + * The name of the relation to create/replace + */ + newRelation: string; + /** + * The Uri of the qlo to run + */ + qloUri: string; +} + +/** + * Parameters to clear the cache + */ +export interface ClearCacheParams { + /** + * The dataset for which we want to clear the cache + */ + db: Dataset; + /** + * Whether the cache should actually be cleared. + */ + dryRun: boolean; +} +/** + * Parameters for trimming the cache of a dataset + */ +export interface TrimCacheParams { + /** + * The dataset that we want to trim the cache of. + */ + db: Dataset; +} + + +/** + * A ql dataset + */ +export interface Dataset { + /** + * The path to the dataset + */ + dbDir: string; + /** + * The name of the working set (normally "default") + */ + workingSet: string; +} + +/** + * The result of trimming or clearing the cache. + */ +export interface ClearCacheResult { + /** + * A user friendly message saying what was or would be + * deleted. + */ + deletionMessage: string; +} + +/** + * Parameters for running a set of queries + */ +export interface EvaluateQueriesParams { + /** + * The dataset to run on + */ + db: Dataset; + /** + * An identifier used in callbacks to identify this run. + */ + evaluateId: number; + /** + * The queries to run + */ + queries: QueryToRun[]; + /** + * Whether the evaluator should stop on a non fatal-error + */ + stopOnError: boolean; + /** + * Whether the evaluator should assume this is the final + * run on this dataset before it's cache would be deleted. + */ + useSequenceHint: boolean; +} + +/** + * A single query that should be run + */ +export interface QueryToRun { + /** + * The id of this query within the run + */ + id: number; + /** + * A uri pointing to the qlo to run. + */ + qlo: string; + /** + * The path where we should save this queries results + */ + resultsPath: string; + /** + * The per stage timeout (0 for no timeout) + */ + timeoutSecs: number; + /** + * Values to set for each template + */ + templateValues?: { [key: string]: TemplateSource; }; + /** + * Whether templates without values in the templateValues + * map should be set to the empty set or give an error. + */ + allowUnknownTemplates: boolean; +} + +/** + * The source of templates. Only one + */ +export interface TemplateSource { + /** + * Do basic interpretation of query results and + * use the interpreted results in the query. + * This should only be used to support legacy filter + * queries. + */ + interpretedInput?: ProblemResults; + /** + * Use the explicitly listed values + */ + values?: RelationValues; +} + +/** + * A relation as a list of tuples + */ +export interface RelationValues { + tuples: Value[][]; +} + +/** + * A single primitive value for templates. + * Only one case should be set. + */ +export interface Value { + booleanValue?: boolean; + dateValue?: string; + doubleValue?: number; + intValue?: number; + stringValue?: string; +} + +/** + * A relation made by interpreting the results of a problem or metric query + * to be used as the input to a filter query. + */ +export interface ProblemResults { + /** + * The path to the original query. + */ + queryPath: string; + /** + * The way of obtaining the queries + */ + results: ResultSet; + /** + * Whether the results are for a defect filter or a metric filter. + */ + type: ResultType; +} + +/** + * The type of results that are going to be sent into the filter query. + */ +export enum ResultType { + METRIC = 0, + DEFECT = 1, +} + +/** + * The way of obtaining the results + */ +export interface ResultSet { + /** + * Via an earlier query in the evaluation run + */ + precedingQuery?: number; + /** + * Directly from an existing results set. + */ + resultsFile?: string; +} + +/** + * The type returned when the evaluation is complete + */ +export interface EvaluationComplete { } + +/** + * The result of a single query + */ +export interface EvaluationResult { + /** + * The id of the run that this query was in + */ + runId: number; + /** + * The id of the query within the run + */ + queryId: number; + /** + * The type of the result. See QueryResultType for + * possible meanings. Any other result should be interpreted as an error. + */ + resultType: number; + /** + * The wall clock time it took to evaluate the query. + * The time is from when we initially tried to evaluate the query + * to when we get the results. Hence with parallel evaluation the times may + * look odd. + */ + evaluationTime: number; + /** + * An error message if an error happened + */ + message?: string; +} + +/** + * The result of running a query, + */ +export namespace QueryResultType { + /** + * The query ran successfully + */ + export const SUCCESS = 0; + /** + * The query failed due to an reason + * that isn't listed + */ + export const OTHER_ERROR = 1; + /** + * The query failed due to running out of + * memory + */ + export const OOM = 2; + /** + * The query failed due to exceeding the timeout + */ + export const TIMEOUT = 3; + /** + * The query failed because it was cancelled. + */ + export const CANCELLATION = 4; +} + +/** + * Parameters for running an upgrade + */ +export interface RunUpgradeParams { + /** + * The dataset to upgrade + */ + db: Dataset; + /** + * The per stage timeout in seconds. Use 0 + * for no timeout. + */ + timeoutSecs: number; + /** + * The upgrade to run + */ + toRun: CompiledUpgrades; +} + + +/** + * The result of running an upgrade + */ +export interface RunUpgradeResult { + /** + * The type of the result. See QueryResultType for + * possible meanings. Any other result should be interpreted as an error. + */ + resultType: number; + /** + * The error message if an error occurred + */ + error?: string; + /** + * The new dbscheme sha. + */ + finalSha: string; +} + + +/** + * Type for any action that could have progress messages. + */ +export interface WithProgressId { + /** + * The main body + */ + body: T, + /** + * The id used to report progress updates + */ + progressId: number +} + +export interface ProgressMessage { + /** + * The id of the operation that is running + */ + id: number; + /** + * The current step + */ + step: number; + /** + * The maximum step. This *should* be constant for a single job. + */ + maxStep: number; + /** + * The current progress message + */ + message: string; +} + +/** + * Check a Ql query for errors without compiling it + */ +export const checkQuery = new rpc.RequestType, CheckQueryResult, void, void>('compilation/checkQuery'); +/** + * Compile a Ql query into a qlo + */ +export const compileQuery = new rpc.RequestType, CheckQueryResult, void, void>('compilation/compileQuery'); +/** + * Compile a dil query into a qlo + */ +export const compileDilQuery = new rpc.RequestType, CheckQueryResult, void, void>('compilation/compileDilQuery'); + + +/** + * Check if there is a valid upgrade path between two dbschemes. + */ +export const checkUpgrade = new rpc.RequestType, CheckUpgradeResult, void, void>('compilation/checkUpgrade'); +/** + * Compile an upgrade script to upgrade a dataset. + */ +export const compileUpgrade = new rpc.RequestType, CompileUpgradeResult, void, void>('compilation/compileUpgrade'); + + +/** + * Clear the cache of a dataset + */ +export const clearCache = new rpc.RequestType, ClearCacheResult, void, void>('evaluation/clearCache'); +/** + * Trim the cache of a dataset + */ +export const trimCache = new rpc.RequestType, ClearCacheResult, void, void>('evaluation/trimCache'); + +/** + * Run some queries on a dataset + */ +export const runQueries = new rpc.RequestType, EvaluationComplete, void, void>('evaluation/runQueries'); + +/** + * Run upgrades on a dataset + */ +export const runUpgrade = new rpc.RequestType, RunUpgradeResult, void, void>('evaluation/runUpgrade'); + +/** + * Request returned to the client to notify completion of a query. + * The full runQueries job is completed when all queries are acknowledged. + */ +export const completeQuery = new rpc.RequestType('evaluation/queryCompleted'); + +/** + * A notification that the progress has been changed. + */ +export const progress = new rpc.NotificationType('ql/progressUpdated'); diff --git a/extensions/ql-vscode/src/queries.ts b/extensions/ql-vscode/src/queries.ts new file mode 100644 index 000000000..4441bfe55 --- /dev/null +++ b/extensions/ql-vscode/src/queries.ts @@ -0,0 +1,663 @@ +import * as crypto from 'crypto'; +import * as fs from 'fs-extra'; +import * as path from 'path'; +import * as sarif from 'sarif'; +import * as tmp from 'tmp'; +import * as vscode from 'vscode'; +import * as cli from './cli'; +import { DatabaseItem } from './databases'; +import * as helpers from './helpers'; +import { DatabaseInfo, SortState, ResultsInfo, SortedResultSetInfo } from './interface-types'; +import { logger } from './logging'; +import * as messages from './messages'; +import * as qsClient from './queryserver-client'; +import { promisify } from 'util'; + +/** + * queries.ts + * ------------- + * + * Compiling and running QL queries. + */ + +// XXX: Tmp directory should be configuarble. +export const tmpDir = tmp.dirSync({ prefix: 'queries_', keep: false, unsafeCleanup: true }); +const upgradesTmpDir = tmp.dirSync({ dir: tmpDir.name, prefix: 'upgrades_', keep: false, unsafeCleanup: true }); +export const tmpDirDisposal = { + dispose: () => { + upgradesTmpDir.removeCallback(); + tmpDir.removeCallback(); + } +}; + +let queryCounter = 0; + +export class UserCancellationException extends Error { } + +/** + * A collection of evaluation-time information about a query, + * including the query itself, and where we have decided to put + * temporary files associated with it, such as the compiled query + * output and results. + */ +export class QueryInfo { + compiledQueryPath: string; + resultsInfo: ResultsInfo; + /** + * Map from result set name to SortedResultSetInfo. + */ + sortedResultsInfo: Map; + dataset: vscode.Uri; // guarantee the existence of a well-defined dataset dir at this point + + constructor( + public program: messages.QlProgram, + public dbItem: DatabaseItem, + public queryDbscheme: string, // the dbscheme file the query expects, based on library path resolution + public quickEvalPosition?: messages.Position, + public metadata?: cli.QueryMetadata, + ) { + this.compiledQueryPath = path.join(tmpDir.name, `compiledQuery${queryCounter}.qlo`); + this.resultsInfo = { + resultsPath: path.join(tmpDir.name, `results${queryCounter}.bqrs`), + interpretedResultsPath: path.join(tmpDir.name, `interpretedResults${queryCounter}.sarif`) + }; + this.sortedResultsInfo = new Map(); + if (dbItem.contents === undefined) { + throw new Error('Can\'t run query on invalid database.'); + } + this.dataset = dbItem.contents.datasetUri; + queryCounter++; + } + + async run( + qs: qsClient.QueryServerClient, + ): Promise { + let result: messages.EvaluationResult | null = null; + + const callbackId = qs.registerCallback(res => { result = res }); + + const queryToRun: messages.QueryToRun = { + resultsPath: this.resultsInfo.resultsPath, + qlo: vscode.Uri.file(this.compiledQueryPath).toString(), + allowUnknownTemplates: true, + id: callbackId, + timeoutSecs: qs.config.timeoutSecs, + } + const dataset: messages.Dataset = { + dbDir: this.dataset.fsPath, + workingSet: 'default' + } + const params: messages.EvaluateQueriesParams = { + db: dataset, + evaluateId: callbackId, + queries: [queryToRun], + stopOnError: false, + useSequenceHint: false + } + try { + await helpers.withProgress({ + location: vscode.ProgressLocation.Notification, + title: "Running Query", + cancellable: true, + }, (progress, token) => { + return qs.sendRequest(messages.runQueries, params, token, progress) + }); + } finally { + qs.unRegisterCallback(callbackId); + } + return result || { evaluationTime: 0, message: "No result from server", queryId: -1, runId: callbackId, resultType: messages.QueryResultType.OTHER_ERROR }; + } + + async compile( + qs: qsClient.QueryServerClient, + ): Promise { + let compiled: messages.CheckQueryResult; + try { + const params: messages.CompileQueryParams = { + compilationOptions: { + computeNoLocationUrls: true, + failOnWarnings: false, + fastCompilation: false, + includeDilInQlo: true, + localChecking: false, + noComputeGetUrl: false, + noComputeToString: false, + }, + extraOptions: { + timeoutSecs: qs.config.timeoutSecs + }, + queryToCheck: this.program, + resultPath: this.compiledQueryPath, + target: !!this.quickEvalPosition ? { quickEval: { quickEvalPos: this.quickEvalPosition } } : { query: {} } + }; + + + compiled = await helpers.withProgress({ + location: vscode.ProgressLocation.Notification, + title: "Compiling Query", + cancellable: true, + }, (progress, token) => { + return qs.sendRequest(messages.compileQuery, params, token, progress); + }); + } finally { + qs.logger.log(" - - - COMPILATION DONE - - - "); + } + + return (compiled.messages || []).filter(msg => msg.severity == 0); + } + + /** + * Holds if this query should produce interpreted results. + */ + hasInterpretedResults(): boolean { + return this.dbItem.hasDbInfo(); + } + + async updateSortState(server: cli.CodeQLCliServer, resultSetName: string, sortState: SortState | undefined): Promise { + if (sortState === undefined) { + this.sortedResultsInfo.delete(resultSetName); + return; + } + + const sortedResultSetInfo: SortedResultSetInfo = { + resultsPath: path.join(tmpDir.name, `sortedResults${queryCounter}-${resultSetName}.bqrs`), + sortState + }; + + await server.sortBqrs(this.resultsInfo.resultsPath, sortedResultSetInfo.resultsPath, resultSetName, [sortState.columnIndex], [sortState.direction]); + this.sortedResultsInfo.set(resultSetName, sortedResultSetInfo); + } +} + +/** + * Call cli command to interpret results. + */ +export async function interpretResults(server: cli.CodeQLCliServer, queryInfo: QueryInfo, resultsInfo: ResultsInfo, sourceInfo?: cli.SourceInfo): Promise { + if (await fs.pathExists(resultsInfo.interpretedResultsPath)) { + return JSON.parse(await fs.readFile(resultsInfo.interpretedResultsPath, 'utf8')); + } + const { metadata } = queryInfo; + if (metadata == undefined) { + throw new Error('Can\'t interpret results without query metadata'); + } + let { kind, id } = metadata; + if (kind == undefined) { + throw new Error('Can\'t interpret results without query metadata including kind'); + } + if (id == undefined) { + // Interpretation per se doesn't really require an id, but the + // SARIF format does, so in the absence of one, we invent one + // based on the query path. + // + // Just to be careful, sanitize to remove '/' since SARIF (section + // 3.27.5 "ruleId property") says that it has special meaning. + id = queryInfo.program.queryPath.replace(/\//g, '-'); + } + return await server.interpretBqrs({ kind, id }, resultsInfo.resultsPath, resultsInfo.interpretedResultsPath, sourceInfo); +} + +export interface EvaluationInfo { + query: QueryInfo; + result: messages.EvaluationResult; + database: DatabaseInfo; +} + +/** + * Checks whether the given database can be upgraded to the given target DB scheme, + * and whether the user wants to proceed with the upgrade. + * Reports errors to both the user and the console. + * @returns the `UpgradeParams` needed to start the upgrade, if the upgrade is possible and was confirmed by the user, or `undefined` otherwise. + */ +async function checkAndConfirmDatabaseUpgrade(qs: qsClient.QueryServerClient, db: DatabaseItem, targetDbScheme: vscode.Uri, upgradesDirectories: vscode.Uri[]): + Promise { + if (db.contents === undefined || db.contents.dbSchemeUri === undefined) { + helpers.showAndLogErrorMessage("Database is invalid, and cannot be upgraded."); + return; + } + const params: messages.UpgradeParams = { + fromDbscheme: db.contents.dbSchemeUri.fsPath, + toDbscheme: targetDbScheme.fsPath, + additionalUpgrades: upgradesDirectories.map(uri => uri.fsPath) + }; + + let checkUpgradeResult: messages.CheckUpgradeResult; + try { + qs.logger.log('Checking database upgrade...'); + checkUpgradeResult = await checkDatabaseUpgrade(qs, params); + } + catch (e) { + helpers.showAndLogErrorMessage(`Database cannot be upgraded: ${e}`); + return; + } + finally { + qs.logger.log('Done checking database upgrade.'); + } + + const checkedUpgrades = checkUpgradeResult.checkedUpgrades; + if (checkedUpgrades === undefined) { + const error = checkUpgradeResult.upgradeError || '[no error message available]'; + await helpers.showAndLogErrorMessage(`Database cannot be upgraded: ${error}`); + return; + } + + if (checkedUpgrades.scripts.length === 0) { + await helpers.showAndLogInformationMessage('Database is already up to date; nothing to do.'); + return; + } + + let curSha = checkedUpgrades.initialSha; + let descriptionMessage = ''; + for (const script of checkedUpgrades.scripts) { + descriptionMessage += `Would perform upgrade: ${script.description}\n`; + descriptionMessage += `\t-> Compatibility: ${script.compatibility}\n`; + curSha = script.newSha; + } + + const targetSha = checkedUpgrades.targetSha; + if (curSha != targetSha) { + // Newlines aren't rendered in notifications: https://github.com/microsoft/vscode/issues/48900 + // A modal dialog would be rendered better, but is more intrusive. + await helpers.showAndLogErrorMessage(`Database cannot be upgraded to the target database scheme. + Can upgrade from ${checkedUpgrades.initialSha} (current) to ${curSha}, but cannot reach ${targetSha} (target).`); + // TODO: give a more informative message if we think the DB is ahead of the target DB scheme + return; + } + + logger.log(descriptionMessage); + // Ask the user to confirm the upgrade. + const shouldUpgrade = await helpers.showBinaryChoiceDialog(`Should the database ${db.databaseUri.fsPath} be upgraded?\n\n${descriptionMessage}`); + if (shouldUpgrade) { + return params; + } + else { + throw new UserCancellationException('User cancelled the database upgrade.'); + } +} + +/** + * Command handler for 'Upgrade Database'. + * Attempts to upgrade the given database to the given target DB scheme, using the given directory of upgrades. + * First performs a dry-run and prompts the user to confirm the upgrade. + * Reports errors during compilation and evaluation of upgrades to the user. + */ +export async function upgradeDatabase(qs: qsClient.QueryServerClient, db: DatabaseItem, targetDbScheme: vscode.Uri, upgradesDirectories: vscode.Uri[]): + Promise { + const upgradeParams = await checkAndConfirmDatabaseUpgrade(qs, db, targetDbScheme, upgradesDirectories); + + if (upgradeParams === undefined) { + return; + } + + let compileUpgradeResult: messages.CompileUpgradeResult; + try { + compileUpgradeResult = await compileDatabaseUpgrade(qs, upgradeParams); + } + catch (e) { + helpers.showAndLogErrorMessage(`Compilation of database upgrades failed: ${e}`); + return; + } + finally { + qs.logger.log('Done compiling database upgrade.') + } + + if (compileUpgradeResult.compiledUpgrades === undefined) { + const error = compileUpgradeResult.error || '[no error message available]'; + helpers.showAndLogErrorMessage(`Compilation of database upgrades failed: ${error}`); + return; + } + + try { + qs.logger.log('Running the following database upgrade:'); + qs.logger.log(compileUpgradeResult.compiledUpgrades.scripts.map(s => s.description.description).join('\n')); + return await runDatabaseUpgrade(qs, db, compileUpgradeResult.compiledUpgrades); + } + catch (e) { + helpers.showAndLogErrorMessage(`Database upgrade failed: ${e}`); + return; + } + finally { + qs.logger.log('Done running database upgrade.') + } +} + +async function checkDatabaseUpgrade(qs: qsClient.QueryServerClient, upgradeParams: messages.UpgradeParams): + Promise { + return helpers.withProgress({ + location: vscode.ProgressLocation.Notification, + title: "Checking for database upgrades", + cancellable: true, + }, (progress, token) => qs.sendRequest(messages.checkUpgrade, upgradeParams, token, progress)); +} + +async function compileDatabaseUpgrade(qs: qsClient.QueryServerClient, upgradeParams: messages.UpgradeParams): + Promise { + const params: messages.CompileUpgradeParams = { + upgrade: upgradeParams, + upgradeTempDir: upgradesTmpDir.name + } + + return helpers.withProgress({ + location: vscode.ProgressLocation.Notification, + title: "Compiling database upgrades", + cancellable: true, + }, (progress, token) => qs.sendRequest(messages.compileUpgrade, params, token, progress)); +} + +async function runDatabaseUpgrade(qs: qsClient.QueryServerClient, db: DatabaseItem, upgrades: messages.CompiledUpgrades): + Promise { + + if (db.contents === undefined || db.contents.datasetUri === undefined) { + throw new Error('Can\'t upgrade an invalid database.'); + } + const database: messages.Dataset = { + dbDir: db.contents.datasetUri.fsPath, + workingSet: 'default' + }; + + const params: messages.RunUpgradeParams = { + db: database, + timeoutSecs: qs.config.timeoutSecs, + toRun: upgrades + }; + + return helpers.withProgress({ + location: vscode.ProgressLocation.Notification, + title: "Running database upgrades", + cancellable: true, + }, (progress, token) => qs.sendRequest(messages.runUpgrade, params, token, progress)); +} + +export async function clearCacheInDatabase(qs: qsClient.QueryServerClient, dbItem: DatabaseItem): + Promise { + if (dbItem.contents === undefined) { + throw new Error('Can\'t clear the cache in an invalid database.'); + } + + const db: messages.Dataset = { + dbDir: dbItem.contents.datasetUri.fsPath, + workingSet: 'default', + }; + + const params: messages.ClearCacheParams = { + dryRun: false, + db, + }; + + return helpers.withProgress({ + location: vscode.ProgressLocation.Notification, + title: "Clearing Cache", + cancellable: false, + }, (progress, token) => + qs.sendRequest(messages.clearCache, params, token, progress) + ); +} + +/** + * + * @param filePath This needs to be equivalent to java Path.toRealPath(NO_FOLLOW_LINKS) + * + */ +async function convertToQlPath(filePath: string): Promise { + if (process.platform === "win32") { + + if (path.parse(filePath).root === filePath) { + // Java assumes uppercase drive letters are canonical. + return filePath.toUpperCase(); + } else { + const dir = await convertToQlPath(path.dirname(filePath)); + const fileName = path.basename(filePath); + const fileNames = await promisify(fs.readdir)(dir); + for (const name of fileNames) { + if (fileName.localeCompare(name, undefined, { sensitivity: 'accent' }) === 0) { + return path.join(dir, name); + } + } + } + throw new Error("Can't convert path to form suitable for QL:" + filePath); + } else { + return filePath; + } +} + + + +/** Gets the selected position within the given editor. */ +async function getSelectedPosition(editor: vscode.TextEditor): Promise { + const pos = editor.selection.start; + const posEnd = editor.selection.end; + // Convert from 0-based to 1-based line and column numbers. + return { + fileName: await convertToQlPath(editor.document.fileName), + line: pos.line + 1, column: pos.character + 1, + endLine: posEnd.line + 1, endColumn: posEnd.character + 1 + }; +} + +/** + * Compare the dbscheme implied by the query `query` and that of the current database. + * If they are compatible, do nothing. + * If they are incompatible but the database can be upgraded, suggest that upgrade. + * If they are incompatible and the database cannot be upgraded, throw an error. + */ +async function checkDbschemeCompatibility( + cliServer: cli.CodeQLCliServer, + qs: qsClient.QueryServerClient, + query: QueryInfo +): Promise { + const searchPath = helpers.getOnDiskWorkspaceFolders(); + + if (query.dbItem.contents !== undefined && query.dbItem.contents.dbSchemeUri !== undefined) { + const info = await cliServer.resolveUpgrades(query.dbItem.contents.dbSchemeUri.fsPath, searchPath); + async function hash(filename: string): Promise { + return crypto.createHash('sha256').update(await fs.readFile(filename)).digest('hex'); + } + + // At this point, we have learned about three dbschemes: + + // query.program.dbschemePath is the dbscheme of the actual + // database we're querying. + const dbschemeOfDb = await hash(query.program.dbschemePath); + + // query.queryDbScheme is the dbscheme of the query we're + // running, including the library we've resolved it to use. + const dbschemeOfLib = await hash(query.queryDbscheme); + + // info.finalDbscheme is which database we're able to upgrade to + const upgradableTo = await hash(info.finalDbscheme); + + if (upgradableTo != dbschemeOfLib) { + logger.log(`Query ${query.program.queryPath} expects database scheme ${query.queryDbscheme}, but database has scheme ${query.program.dbschemePath}, and no upgrade path found`); + throw new Error(`Query ${query.program.queryPath} expects database scheme ${query.queryDbscheme}, but the current database has a different scheme, and no database upgrades are available. The current database scheme may be newer than the CodeQL query libraries in your workspace. Please try using a newer version of the query libraries.`); + } + + if (upgradableTo == dbschemeOfLib && + dbschemeOfDb != dbschemeOfLib) { + // Try to upgrade the database + await upgradeDatabase( + qs, + query.dbItem, + vscode.Uri.file(info.finalDbscheme), + searchPath.map(file => vscode.Uri.file(file)) + ); + } + } +} + +/** Prompts the user to save `document` if it has unsaved changes. */ +async function promptUserToSaveChanges(document: vscode.TextDocument) { + if (document.isDirty) { + // TODO: add 'always save' button which records preference in configuration + if (await helpers.showBinaryChoiceDialog('Query file has unsaved changes. Save now?')) { + await document.save(); + } + } +} + +type SelectedQuery = { + queryPath: string, + quickEvalPosition?: messages.Position +}; + +/** + * Determines which QL file to run during an invocation of `Run Query` or `Quick Evaluation`, as follows: + * - If the command was called by clicking on a file, then use that file. + * - Otherwise, use the file open in the current editor. + * - In either case, prompt the user to save the file if it is open with unsaved changes. + * - For `Quick Evaluation`, ensure the selected file is also the one open in the editor, + * and use the selected region. + * @param selectedResourceUri The selected resource when the command was run. + * @param quickEval Whether the command being run is `Quick Evaluation`. +*/ +async function determineSelectedQuery(selectedResourceUri: vscode.Uri | undefined, quickEval: boolean): Promise { + const editor = vscode.window.activeTextEditor; + + // Choose which QL file to use. + let queryUri: vscode.Uri; + if (selectedResourceUri === undefined) { + // No resource was passed to the command handler, so obtain it from the active editor. + // This usually happens when the command is called from the Command Palette. + if (editor === undefined) { + throw new Error('No query was selected. Please select a query and try again.'); + } else { + queryUri = editor.document.uri; + } + } else { + // A resource was passed to the command handler, so use it. + queryUri = selectedResourceUri; + } + + if (queryUri.scheme !== 'file') { + throw new Error('Can only run queries that are on disk.'); + } + const queryPath = queryUri.fsPath; + + // Whether we chose the file from the active editor or from a context menu, + // if the same file is open with unsaved changes in the active editor, + // then prompt the user to save it first. + if (editor !== undefined && editor.document.uri.fsPath === queryPath) { + await promptUserToSaveChanges(editor.document); + } + + let quickEvalPosition: messages.Position | undefined = undefined; + if (quickEval) { + if (editor == undefined) { + throw new Error('Can\'t run quick evaluation without an active editor.'); + } + if (editor.document.fileName !== queryPath) { + // For Quick Evaluation we expect these to be the same. + // Report an error if we end up in this (hopefully unlikely) situation. + throw new Error('The selected resource for quick evaluation should match the active editor.'); + } + quickEvalPosition = await getSelectedPosition(editor); + } + + return { queryPath, quickEvalPosition }; +} + +export async function compileAndRunQueryAgainstDatabase( + cliServer: cli.CodeQLCliServer, + qs: qsClient.QueryServerClient, + db: DatabaseItem, + quickEval: boolean, + selectedQueryUri: vscode.Uri | undefined +): Promise { + + if (!db.contents || !db.contents.dbSchemeUri) { + throw new Error(`Database ${db.databaseUri} does not have a CodeQL database scheme.`); + } + + // Determine which query to run, based on the selection and the active editor. + const { queryPath, quickEvalPosition } = await determineSelectedQuery(selectedQueryUri, quickEval); + + // Get the workspace folder paths. + const diskWorkspaceFolders = helpers.getOnDiskWorkspaceFolders(); + // Figure out the library path for the query. + const packConfig = await cliServer.resolveLibraryPath(diskWorkspaceFolders, queryPath); + + // Check whether the query has an entirely different schema from the + // database. (Queries that merely need the database to be upgraded + // won't trigger this check) + // This test will produce confusing results if we ever change the name of the database schema files. + const querySchemaName = path.basename(packConfig.dbscheme); + const dbSchemaName = path.basename(db.contents.dbSchemeUri.fsPath); + if (querySchemaName != dbSchemaName) { + logger.log(`Query schema was ${querySchemaName}, but database schema was ${dbSchemaName}.`); + throw new Error(`The query ${path.basename(queryPath)} cannot be run against the selected database: their target languages are different. Please select a different database and try again.`); + } + + const qlProgram: messages.QlProgram = { + // The project of the current document determines which library path + // we use. The `libraryPath` field in this server message is relative + // to the workspace root, not to the project root. + libraryPath: packConfig.libraryPath, + // Since we are compiling and running a query against a database, + // we use the database's DB scheme here instead of the DB scheme + // from the current document's project. + dbschemePath: db.contents.dbSchemeUri.fsPath, + queryPath: queryPath + }; + + // Read the query metadata if possible, to use in the UI. + let metadata: cli.QueryMetadata | undefined; + try { + metadata = await cliServer.resolveMetadata(qlProgram.queryPath); + } catch (e) { + // Ignore errors and provide no metadata. + logger.log(`Couldn't resolve metadata for ${qlProgram.queryPath}: ${e}`); + } + + const query = new QueryInfo(qlProgram, db, packConfig.dbscheme, quickEvalPosition, metadata); + await checkDbschemeCompatibility(cliServer, qs, query); + + const errors = await query.compile(qs); + + + if (errors.length == 0) { + const result = await query.run(qs); + return { + query, + result, + database: { + name: db.name, + databaseUri: db.databaseUri.toString(true) + } + }; + } else { + // Error dialogs are limited in size and scrollability, + // so we include a general description of the problem, + // and direct the user to the output window for the detailed compilation messages. + // However we don't show quick eval errors there so we need to display them anyway. + qs.logger.log(`Failed to compile query ${query.program.queryPath} against database scheme ${query.program.dbschemePath}:`); + + let formattedMessages: string[] = []; + + for (const error of errors) { + const message = error.message || "[no error message available]"; + const formatted = `ERROR: ${message} (${error.position.fileName}:${error.position.line}:${error.position.column}:${error.position.endLine}:${error.position.endColumn})`; + formattedMessages.push(formatted); + qs.logger.log(formatted); + } + if (quickEval && formattedMessages.length <= 3) { + helpers.showAndLogErrorMessage("Quick evaluation compilation failed: \n" + formattedMessages.join("\n")); + } else { + helpers.showAndLogErrorMessage((quickEval ? "Quick evaluation" : "Query") + + " compilation failed. Please make sure there are no errors in the query, the database is up to date," + + " and the query and database use the same target language. For more details on the error, go to View > Output," + + " and choose CodeQL Query Server from the dropdown."); + } + return { + query, + result: { + evaluationTime: 0, + resultType: messages.QueryResultType.OTHER_ERROR, + queryId: -1, + runId: -1, + message: "Query had compilation errors" + }, + database: { + name: db.name, + databaseUri: db.databaseUri.toString(true) + } + }; + } +} diff --git a/extensions/ql-vscode/src/query-history.ts b/extensions/ql-vscode/src/query-history.ts new file mode 100644 index 000000000..444619bad --- /dev/null +++ b/extensions/ql-vscode/src/query-history.ts @@ -0,0 +1,182 @@ +import * as vscode from 'vscode'; +import { ExtensionContext, window as Window } from 'vscode'; +import { EvaluationInfo } from './queries'; +import * as helpers from './helpers'; +import * as messages from './messages'; +/** + * query-history.ts + * ------------ + * Managing state of previous queries that we've executed. + * + * The source of truth of the current state resides inside the + * `TreeDataProvider` subclass below. + */ + +/** + * One item in the user-displayed list of queries that have been run. + */ +export class QueryHistoryItem { + queryName: string; + time: string; + databaseName: string; + info: EvaluationInfo; + + constructor(info: EvaluationInfo) { + this.queryName = helpers.getQueryName(info); + this.databaseName = info.database.name; + this.info = info; + this.time = new Date().toLocaleString(); + } + + get statusString(): string { + switch (this.info.result.resultType) { + case messages.QueryResultType.CANCELLATION: + return `cancelled after ${this.info.result.evaluationTime / 1000} seconds`; + case messages.QueryResultType.OOM: + return `out of memory`; + case messages.QueryResultType.SUCCESS: + return `finished in ${this.info.result.evaluationTime / 1000} seconds`; + case messages.QueryResultType.TIMEOUT: + return `timed out after ${this.info.result.evaluationTime / 1000} seconds`; + case messages.QueryResultType.OTHER_ERROR: + default: + return `failed`; + } + } + + toString(): string { + const { databaseName, queryName, time } = this; + return `[${time}] ${queryName} on ${databaseName} - ${this.statusString}`; + } +} + +/** + * Tree data provider for the query history view. + */ +class HistoryTreeDataProvider implements vscode.TreeDataProvider { + + /** + * XXX: This idiom for how to get a `.fire()`-able event emitter was + * cargo culted from another vscode extension. It seems rather + * involved and I hope there's something better that can be done + * instead. + */ + private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; + + private ctx: ExtensionContext; + private history: QueryHistoryItem[] = []; + + /** + * When not undefined, must be reference-equal to an item in `this.databases`. + */ + private current: QueryHistoryItem | undefined; + + constructor(ctx: ExtensionContext) { + this.ctx = ctx; + this.history = []; + } + + getTreeItem(element: QueryHistoryItem): vscode.TreeItem { + const it = new vscode.TreeItem(element.toString()); + + it.command = { + title: 'Query History Item', + command: 'codeQLQueryHistory.itemClicked', + arguments: [element], + }; + + return it; + } + + getChildren(element?: QueryHistoryItem): vscode.ProviderResult { + if (element == undefined) { + return this.history; + } + else { + return []; + } + } + + getParent(element: QueryHistoryItem): vscode.ProviderResult { + return null; + } + + getCurrent(): QueryHistoryItem | undefined { + return this.current; + } + + push(item: QueryHistoryItem): void { + this.current = item; + this.history.push(item); + this._onDidChangeTreeData.fire(); + } + + setCurrentItem(item: QueryHistoryItem) { + this.current = item; + } +} + +/** + * Number of milliseconds two clicks have to arrive apart to be + * considered a double-click. + */ +const DOUBLE_CLICK_TIME = 500; + +export class QueryHistoryManager { + treeDataProvider: HistoryTreeDataProvider; + ctx: ExtensionContext; + treeView: vscode.TreeView; + selectedCallback: ((item: QueryHistoryItem) => void) | undefined; + lastItemClick: { time: Date, item: QueryHistoryItem } | undefined; + + async handleOpenQuery(queryHistoryItem: QueryHistoryItem) { + const textDocument = await vscode.workspace.openTextDocument(vscode.Uri.file(queryHistoryItem.info.query.program.queryPath)); + await vscode.window.showTextDocument(textDocument, vscode.ViewColumn.One); + } + + async handleItemClicked(queryHistoryItem: QueryHistoryItem) { + this.treeDataProvider.setCurrentItem(queryHistoryItem); + + const now = new Date(); + const prevItemClick = this.lastItemClick; + this.lastItemClick = { time: now, item: queryHistoryItem }; + + if (prevItemClick !== undefined + && (now.valueOf() - prevItemClick.time.valueOf()) < DOUBLE_CLICK_TIME + && queryHistoryItem == prevItemClick.item) { + // show original query file on double click + await this.handleOpenQuery(queryHistoryItem); + } + else { + // show results on single click + if (this.selectedCallback !== undefined) { + const sc = this.selectedCallback; + await sc(queryHistoryItem); + } + } + } + + constructor(ctx: ExtensionContext, selectedCallback?: (item: QueryHistoryItem) => Promise) { + this.ctx = ctx; + this.selectedCallback = selectedCallback; + const treeDataProvider = this.treeDataProvider = new HistoryTreeDataProvider(ctx); + this.treeView = Window.createTreeView('codeQLQueryHistory', { treeDataProvider }); + this.treeView.onDidChangeSelection(async ev => { + if (ev.selection.length == 0) { + const current = this.treeDataProvider.getCurrent(); + if (current != undefined) + this.treeView.reveal(current); // don't allow selection to become empty + } + }); + ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.openQuery', this.handleOpenQuery)); + ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.itemClicked', async (item) => { + return this.handleItemClicked(item); + })); + } + + push(item: QueryHistoryItem) { + this.treeDataProvider.push(item); + this.treeView.reveal(item, { select: true }); + } +} diff --git a/extensions/ql-vscode/src/queryserver-client.ts b/extensions/ql-vscode/src/queryserver-client.ts new file mode 100644 index 000000000..0ea420fa4 --- /dev/null +++ b/extensions/ql-vscode/src/queryserver-client.ts @@ -0,0 +1,163 @@ +import * as cp from 'child_process'; +import { DisposableObject } from 'semmle-vscode-utils'; +import { Disposable } from 'vscode'; +import { CancellationToken, createMessageConnection, MessageConnection, RequestType } from 'vscode-jsonrpc'; +import * as cli from './cli'; +import { QueryServerConfig } from './config'; +import { Logger, ProgressReporter } from './logging'; +import { completeQuery, EvaluationResult, progress, ProgressMessage, WithProgressId } from './messages'; + +type ServerOpts = { + logger: Logger +} + +/** A running query server process and its associated message connection. */ +class ServerProcess implements Disposable { + child: cp.ChildProcess; + connection: MessageConnection; + logger: Logger; + + constructor(child: cp.ChildProcess, connection: MessageConnection, logger: Logger) { + this.child = child; + this.connection = connection; + this.logger = logger; + } + + dispose() { + this.logger.log('Stopping query server...'); + this.connection.dispose(); + this.child.stdin!.end(); + this.child.stderr!.destroy(); + // TODO kill the process if it doesn't terminate after a certain time limit. + + // On Windows, we usually have to terminate the process before closing its stdout. + this.child.stdout!.destroy(); + this.logger.log('Stopped query server.'); + } +} + +type WithProgressReporting = (task: (progress: ProgressReporter, token: CancellationToken) => Thenable) => Thenable; + +/** + * Client that manages a query server process. + * The server process is started upon initialization and tracked during its lifetime. + * The server process is disposed when the client is disposed, or if the client asks + * to restart it (which disposes the existing process and starts a new one). + */ +export class QueryServerClient extends DisposableObject { + serverProcess?: ServerProcess; + evaluationResultCallbacks: { [key: number]: (res: EvaluationResult) => void }; + progressCallbacks: { [key: number]: ((res: ProgressMessage) => void) | undefined }; + nextCallback: number; + nextProgress: number; + withProgressReporting: WithProgressReporting; + + constructor(readonly config: QueryServerConfig, readonly cliServer: cli.CodeQLCliServer, readonly opts: ServerOpts, withProgressReporting: WithProgressReporting) { + super(); + // When the query server configuration changes, restart the query server. + if (config.onDidChangeQueryServerConfiguration !== undefined) { + this.push(config.onDidChangeQueryServerConfiguration(async () => await this.restartQueryServer(), this)); + } + this.withProgressReporting = withProgressReporting; + this.nextCallback = 0; + this.nextProgress = 0; + this.progressCallbacks = {}; + this.evaluationResultCallbacks = {}; + } + + get logger() { return this.opts.logger; } + + /** Stops the query server by disposing of the current server process. */ + private stopQueryServer() { + if (this.serverProcess !== undefined) { + this.disposeAndStopTracking(this.serverProcess); + } else { + this.logger.log('No server process to be stopped.') + } + } + + /** Restarts the query server by disposing of the current server process and then starting a new one. */ + private async restartQueryServer() { + this.logger.log('Restarting query server due to configuration changes...'); + this.stopQueryServer(); + await this.startQueryServer(); + } + + /** Starts a new query server process, sending progress messages to the status bar. */ + async startQueryServer() { + // Use an arrow function to preserve the value of `this`. + return this.withProgressReporting((progress, _) => this.startQueryServerImpl(progress)); + } + + /** Starts a new query server process, sending progress messages to the given reporter. */ + private async startQueryServerImpl(progressReporter: ProgressReporter) { + const ramArgs = await this.cliServer.resolveRam(this.config.queryMemoryMb, progressReporter); + const args = ['--threads', this.config.numThreads.toString()].concat(ramArgs); + if (this.config.debug) { + args.push('--debug', '--tuple-counting'); + } + const child = cli.spawnServer( + this.config.codeQlPath, + 'CodeQL query server', + ['execute', 'query-server'], + args, + this.logger, + data => this.logger.logWithoutTrailingNewline(data.toString()), + undefined, // no listener for stdout + progressReporter + ); + progressReporter.report({ message: 'Connecting to CodeQL query server' }); + const connection = createMessageConnection(child.stdout, child.stdin); + connection.onRequest(completeQuery, res => { + if (!(res.runId in this.evaluationResultCallbacks)) { + this.logger.log(`No callback associated with run id ${res.runId}, continuing without executing any callback`); + } + else { + this.evaluationResultCallbacks[res.runId](res); + } + return {}; + }) + connection.onNotification(progress, res => { + let callback = this.progressCallbacks[res.id]; + if (callback) { + callback(res); + } + }) + this.serverProcess = new ServerProcess(child, connection, this.opts.logger); + // Ensure the server process is disposed together with this client. + this.track(this.serverProcess); + connection.listen(); + progressReporter.report({ message: 'Connected to CodeQL query server' }); + this.nextCallback = 0; + this.nextProgress = 0; + this.progressCallbacks = {}; + this.evaluationResultCallbacks = {}; + } + + registerCallback(callback: (res: EvaluationResult) => void): number { + const id = this.nextCallback++; + this.evaluationResultCallbacks[id] = callback; + return id; + } + + unRegisterCallback(id: number) { + delete this.evaluationResultCallbacks[id]; + } + + get serverProcessPid(): number { + return this.serverProcess!.child.pid; + } + + async sendRequest(type: RequestType, R, E, RO>, parameter: P, token?: CancellationToken, progress?: (res: ProgressMessage) => void): Promise { + let id = this.nextProgress++; + this.progressCallbacks[id] = progress; + try { + if (this.serverProcess === undefined) { + throw new Error('No query server process found.'); + } + return await this.serverProcess.connection.sendRequest(type, { body: parameter, progressId: id }, token); + } finally { + delete this.progressCallbacks[id]; + } + } +} diff --git a/extensions/ql-vscode/src/view/LICENSE b/extensions/ql-vscode/src/view/LICENSE new file mode 100644 index 000000000..f3e9423f7 --- /dev/null +++ b/extensions/ql-vscode/src/view/LICENSE @@ -0,0 +1,398 @@ +Content from https://github.com/microsoft/vscode-icons used +under the Creative Commons License as follows: + +Attribution 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution 4.0 International Public License ("Public License"). To the +extent this Public License may be interpreted as a contract, You are +granted the Licensed Rights in consideration of Your acceptance of +these terms and conditions, and the Licensor grants You such rights in +consideration of benefits the Licensor receives from making the +Licensed Material available under these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/extensions/ql-vscode/src/view/alert-table.tsx b/extensions/ql-vscode/src/view/alert-table.tsx new file mode 100644 index 000000000..7243d878e --- /dev/null +++ b/extensions/ql-vscode/src/view/alert-table.tsx @@ -0,0 +1,349 @@ +import cx from 'classnames'; +import * as path from 'path'; +import * as React from 'react'; +import * as Sarif from 'sarif'; +import { LocationStyle, ResolvableLocationValue } from 'semmle-bqrs'; +import * as octicons from './octicons'; +import { className, renderLocation, ResultTableProps, selectedClassName, zebraStripe } from './result-table-utils'; +import { PathTableResultSet } from './results'; + +export type PathTableProps = ResultTableProps & { resultSet: PathTableResultSet }; +export interface PathTableState { + expanded: { [k: string]: boolean }; +} + +interface SarifLink { + dest: number + text: string +} + +type ParsedSarifLocation = + | ResolvableLocationValue + // Resolvable locations have a `file` field, but it will sometimes include + // a source location prefix, which contains build-specific information the user + // doesn't really need to see. We ensure that `userVisibleFile` will not contain + // that, and is appropriate for display in the UI. + & { userVisibleFile: string } + | { t: 'NoLocation', hint: string }; + +type SarifMessageComponent = string | SarifLink + +/** + * Unescape "[", "]" and "\\" like in sarif plain text messages + */ +function unescapeSarifText(message: string): string { + return message.replace(/\\\[/g, "[").replace(/\\\]/g, "]").replace(/\\\\/, "\\"); +} + +function parseSarifPlainTextMessage(message: string): SarifMessageComponent[] { + let results: SarifMessageComponent[] = []; + + // We want something like "[linkText](4)", except that "[" and "]" may be escaped. The lookbehind asserts + // that the initial [ is not escaped. Then we parse a link text with "[" and "]" escaped. Then we parse the numerical target. + // Technically we could have any uri in the target but we don't output that yet. + // The possibility of escaping outside the link is not mentioned in the sarif spec but we always output sartif this way. + const linkRegex = /(?<=(?([^\\\]\[]|\\\\|\\\]|\\\[)*)\]\((?[0-9]+)\)/g; + let result: RegExpExecArray | null; + let curIndex = 0; + while ((result = linkRegex.exec(message)) !== null) { + results.push(unescapeSarifText(message.substring(curIndex, result.index))); + const linkText = result.groups!["linkText"]; + const linkTarget = +result.groups!["linkTarget"]; + results.push({ dest: linkTarget, text: unescapeSarifText(linkText) }); + curIndex = result.index + result[0].length; + } + results.push(unescapeSarifText(message.substring(curIndex, message.length))); + return results; +} + +/** + * Computes a path normalized to reflect conventional normalization + * of windows paths into zip archive paths. + * @param sourceLocationPrefix The source location prefix of a database. May be + * unix style `/foo/bar/baz` or windows-style `C:\foo\bar\baz`. + * @param sarifRelativeUri A uri relative to sourceLocationPrefix. + * @returns A string that is valid for the `.file` field of a `FivePartLocation`: + * directory separators are normalized, but drive letters `C:` may appear. + */ +export function getPathRelativeToSourceLocationPrefix(sourceLocationPrefix: string, sarifRelativeUui: string) { + const normalizedSourceLocationPrefix = sourceLocationPrefix.replace(/\\/g, '/'); + return path.join(normalizedSourceLocationPrefix, decodeURIComponent(sarifRelativeUui)); +} + +export class PathTable extends React.Component { + constructor(props: PathTableProps) { + super(props); + this.state = { expanded: {} }; + } + + /** + * Given a list of `indices`, toggle the first, and if we 'open' the + * first item, open all the rest as well. This mimics vscode's file + * explorer tree view behavior. + */ + toggle(e: React.MouseEvent, indices: number[]) { + this.setState(previousState => { + if (previousState.expanded[indices[0]]) { + return { expanded: { ...previousState.expanded, [indices[0]]: false } }; + } + else { + const expanded = { ...previousState.expanded }; + for (const index of indices) { + expanded[index] = true; + } + return { expanded }; + } + }); + e.stopPropagation(); + e.preventDefault(); + } + + render(): JSX.Element { + const { selected, databaseUri, resultSet } = this.props; + + const tableClassName = cx(className, { + [selectedClassName]: selected + }); + + const rows: JSX.Element[] = []; + const { numTruncatedResults, sourceLocationPrefix } = resultSet; + + function renderRelatedLocations(msg: string, relatedLocations: Sarif.Location[]): JSX.Element[] { + const relatedLocationsById: { [k: string]: Sarif.Location } = {}; + for (let loc of relatedLocations) { + relatedLocationsById[loc.id!] = loc; + } + + const result: JSX.Element[] = []; + // match things like `[link-text](related-location-id)` + const parts = parseSarifPlainTextMessage(msg); + + + for (const part of parts) { + if (typeof part === "string") { + result.push({part} ); + } else { + const renderedLocation = renderSarifLocationWithText(part.text, relatedLocationsById[part.dest]); + result.push({renderedLocation} ); + } + } return result; + } + + function renderNonLocation(msg: string | undefined, locationHint: string): JSX.Element | undefined { + if (msg == undefined) + return undefined; + return {msg}; + } + + function parseSarifLocation(loc: Sarif.Location): ParsedSarifLocation { + const physicalLocation = loc.physicalLocation; + if (physicalLocation === undefined) + return { t: 'NoLocation', hint: 'no physical location' }; + if (physicalLocation.artifactLocation === undefined) + return { t: 'NoLocation', hint: 'no artifact location' }; + if (physicalLocation.artifactLocation.uri === undefined) + return { t: 'NoLocation', hint: 'artifact location has no uri' }; + + // This is not necessarily really an absolute uri; it could either be a + // file uri or a relative uri. + const uri = physicalLocation.artifactLocation.uri; + + const fileUriRegex = /^file:/; + const effectiveLocation = uri.match(fileUriRegex) ? + decodeURIComponent(uri.replace(fileUriRegex, '')) : + getPathRelativeToSourceLocationPrefix(sourceLocationPrefix, uri); + const userVisibleFile = uri.match(fileUriRegex) ? + decodeURIComponent(uri.replace(fileUriRegex, '')) : + uri; + + if (physicalLocation.region === undefined) { + // If the region property is absent, the physicalLocation object refers to the entire file. + // Source: https://docs.oasis-open.org/sarif/sarif/v2.1.0/cs01/sarif-v2.1.0-cs01.html#_Toc16012638. + // TODO: Do we get here if we provide a non-filesystem URL? + return { + t: LocationStyle.WholeFile, + file: effectiveLocation, + userVisibleFile, + }; + } else { + const region = physicalLocation.region; + // We assume that the SARIF we're given always has startLine + // This is not mandated by the SARIF spec, but should be true of + // SARIF output by our own tools. + const lineStart = region.startLine!; + + // These defaults are from SARIF 2.1.0 spec, section 3.30.2, "Text Regions" + // https://docs.oasis-open.org/sarif/sarif/v2.1.0/cs01/sarif-v2.1.0-cs01.html#_Ref493492556 + const lineEnd = region.endLine === undefined ? lineStart : region.endLine; + const colStart = region.startColumn === undefined ? 1 : region.startColumn; + + // We also assume that our tools will always supply `endColumn` field, which is + // fortunate, since the SARIF spec says that it defaults to the end of the line, whose + // length we don't know at this point in the code. + // + // It is off by one with respect to the way vscode counts columns in selections. + const colEnd = region.endColumn! - 1; + + return { + t: LocationStyle.FivePart, + file: effectiveLocation, + userVisibleFile, + lineStart, + colStart, + lineEnd, + colEnd, + }; + } + } + + function renderSarifLocationWithText(text: string | undefined, loc: Sarif.Location): JSX.Element | undefined { + const parsedLoc = parseSarifLocation(loc); + switch (parsedLoc.t) { + case 'NoLocation': + return renderNonLocation(text, parsedLoc.hint); + case LocationStyle.FivePart: + case LocationStyle.WholeFile: + return renderLocation(parsedLoc, text, databaseUri); + } + return undefined; + } + + /** + * Render sarif location as a link with the text being simply a + * human-readable form of the location itself. + */ + function renderSarifLocation(loc: Sarif.Location): JSX.Element | undefined { + const parsedLoc = parseSarifLocation(loc); + let shortLocation, longLocation: string; + switch (parsedLoc.t) { + case 'NoLocation': + return renderNonLocation("[no location]", parsedLoc.hint); + case LocationStyle.WholeFile: + shortLocation = `${path.basename(parsedLoc.userVisibleFile)}`; + longLocation = `${parsedLoc.userVisibleFile}`; + return renderLocation(parsedLoc, shortLocation, databaseUri, longLocation); + case LocationStyle.FivePart: + shortLocation = `${path.basename(parsedLoc.userVisibleFile)}:${parsedLoc.lineStart}:${parsedLoc.colStart}`; + longLocation = `${parsedLoc.userVisibleFile}`; + return renderLocation(parsedLoc, shortLocation, databaseUri, longLocation); + } + } + + const toggler: (indices: number[]) => (e: React.MouseEvent) => void = (indices) => { + return (e) => this.toggle(e, indices); + }; + + const noResults = No Results; // TODO: Maybe make this look nicer + + let expansionIndex = 0; + + if (resultSet.sarif.runs.length === 0) return noResults; + if (resultSet.sarif.runs[0].results === undefined) return noResults; + + resultSet.sarif.runs[0].results.forEach((result, resultIndex) => { + const text = result.message.text || '[no text]'; + const msg: JSX.Element[] = + result.relatedLocations === undefined ? + [{text}] : + renderRelatedLocations(text, result.relatedLocations); + + const currentResultExpanded = this.state.expanded[expansionIndex]; + const indicator = currentResultExpanded ? octicons.chevronDown : octicons.chevronRight; + const location = result.locations !== undefined && result.locations.length > 0 && + renderSarifLocation(result.locations[0]); + const locationCells = {location}; + + if (result.codeFlows === undefined) { + rows.push( + + {octicons.info} + + {msg} + + {locationCells} + + ); + } + else { + const paths: Sarif.ThreadFlow[] = []; + for (const codeFlow of result.codeFlows) { + for (const threadFlow of codeFlow.threadFlows) { + paths.push(threadFlow); + } + } + + const indices = paths.length == 1 ? + [expansionIndex, expansionIndex + 1] : /* if there's exactly one path, auto-expand + * the path when expanding the result */ + [expansionIndex]; + + rows.push( + + + {indicator} + + + {octicons.listUnordered} + + + {msg} + + {locationCells} + + ); + expansionIndex++; + + paths.forEach(path => { + const currentPathExpanded = this.state.expanded[expansionIndex]; + if (currentResultExpanded) { + const indicator = currentPathExpanded ? octicons.chevronDown : octicons.chevronRight; + rows.push( + + + {indicator} + + Path + + + ); + } + expansionIndex++; + + if (currentResultExpanded && currentPathExpanded) { + let pathIndex = 1; + for (const step of path.locations) { + const msg = step.location !== undefined && step.location.message !== undefined ? + renderSarifLocationWithText(step.location.message.text, step.location) : + '[no location]'; + const additionalMsg = step.location !== undefined ? + renderSarifLocation(step.location) : + ''; + + const stepIndex = resultIndex + pathIndex; + rows.push( + + + + {pathIndex} + {msg} + {additionalMsg} + ); + pathIndex++; + } + } + }); + + } + }); + + if (numTruncatedResults > 0) { + rows.push( + Too many results to show at once. {numTruncatedResults} result(s) omitted. + ); + } + + return + {rows} +
; + } +} diff --git a/extensions/ql-vscode/src/view/octicons.tsx b/extensions/ql-vscode/src/view/octicons.tsx new file mode 100644 index 000000000..4451357ef --- /dev/null +++ b/extensions/ql-vscode/src/view/octicons.tsx @@ -0,0 +1,22 @@ +import * as React from "react"; + +/** + * These icons come from https://github.com/microsoft/vscode-icons + * and are used under the Creative Commons License, see `LICENSE` + * file in this directory. + */ +export const chevronDown = + +; + +export const chevronRight = + +; + +export const listUnordered = + +; + +export const info = + +; diff --git a/extensions/ql-vscode/src/view/raw-results-table.tsx b/extensions/ql-vscode/src/view/raw-results-table.tsx new file mode 100644 index 000000000..247779a34 --- /dev/null +++ b/extensions/ql-vscode/src/view/raw-results-table.tsx @@ -0,0 +1,118 @@ +import cx from 'classnames'; +import * as React from "react"; +import { className, renderLocation, ResultTableProps, selectedClassName, zebraStripe } from "./result-table-utils"; +import { RawTableResultSet, ResultValue, vscode } from "./results"; +import { assertNever } from "../helpers-pure"; +import { SortDirection, SortState, RAW_RESULTS_LIMIT } from "../interface-types"; + +export type RawTableProps = ResultTableProps & { + resultSet: RawTableResultSet, + sortState?: SortState; +}; + +export class RawTable extends React.Component { + constructor(props: RawTableProps) { + super(props); + } + + render(): React.ReactNode { + const { resultSet, selected, databaseUri } = this.props; + + const tableClassName = cx(className, { + [selectedClassName]: selected + }); + + let dataRows = this.props.resultSet.rows; + let numTruncatedResults = 0; + if (dataRows.length > RAW_RESULTS_LIMIT) { + numTruncatedResults = dataRows.length - RAW_RESULTS_LIMIT; + dataRows = dataRows.slice(0, RAW_RESULTS_LIMIT); + } + + const tableRows = dataRows.map((row, rowIndex) => + + { + [ + {rowIndex + 1}, + ...row.map((value, columnIndex) => + + { + renderTupleValue(value, databaseUri) + } + ) + ] + } + + ); + + if (numTruncatedResults > 0) { + const colSpan = dataRows[0].length + 1; // one row for each data column, plus index column + tableRows.push( + Too many results to show at once. {numTruncatedResults} result(s) omitted. + ); + } + + return + + + { + [ + , + ...resultSet.schema.columns.map((col, index) => { + const displayName = col.name || `[${index}]`; + const sortDirection = this.props.sortState && index === this.props.sortState.columnIndex ? this.props.sortState.direction : undefined; + return ; + }) + ] + } + + + + {tableRows} + +
# this.toggleSortStateForColumn(index)}>{displayName}
; + } + + private toggleSortStateForColumn(index: number) { + const sortState = this.props.sortState; + const prevDirection = sortState && sortState.columnIndex === index ? sortState.direction : undefined; + const nextDirection = nextSortDirection(prevDirection); + const nextSortState = nextDirection === undefined ? undefined : { + columnIndex: index, + direction: nextDirection + }; + vscode.postMessage({ + t: 'changeSort', + resultSetName: this.props.resultSet.schema.name, + sortState: nextSortState + }); + } +} + + +/** + * Render one column of a tuple. + */ +function renderTupleValue(v: ResultValue, databaseUri: string): JSX.Element { + if (typeof v === 'string') { + return {v} + } + else if ('uri' in v) { + return
{v.uri}; + } + else { + return renderLocation(v.location, v.label, databaseUri); + } +} + +function nextSortDirection(direction: SortDirection | undefined): SortDirection { + switch (direction) { + case SortDirection.asc: + return SortDirection.desc; + case SortDirection.desc: + case undefined: + return SortDirection.asc; + default: + return assertNever(direction); + } +} diff --git a/extensions/ql-vscode/src/view/result-table-utils.tsx b/extensions/ql-vscode/src/view/result-table-utils.tsx new file mode 100644 index 000000000..084e0dd11 --- /dev/null +++ b/extensions/ql-vscode/src/view/result-table-utils.tsx @@ -0,0 +1,70 @@ +import * as React from 'react'; +import { LocationValue, ResolvableLocationValue, tryGetResolvableLocation } from 'semmle-bqrs'; +import { SortState } from '../interface-types'; +import { ResultSet, vscode } from './results'; + +export interface ResultTableProps { + selected: boolean; + resultSet: ResultSet; + databaseUri: string; + resultsPath: string | undefined; + sortState?: SortState; +} + +export const className = 'vscode-codeql__result-table'; +export const tableSelectionHeaderClassName = 'vscode-codeql__table-selection-header'; +export const toggleDiagnosticsClassName = `${className}-toggle-diagnostics`; +export const selectedClassName = `${className}--selected`; +export const toggleDiagnosticsSelectedClassName = `${toggleDiagnosticsClassName}--selected`; +export const evenRowClassName = 'vscode-codeql__result-table-row--even'; +export const oddRowClassName = 'vscode-codeql__result-table-row--odd'; +export const pathRowClassName = 'vscode-codeql__result-table-row--path'; + +export function jumpToLocationHandler( + loc: ResolvableLocationValue, + databaseUri: string +): (e: React.MouseEvent) => void { + return (e) => { + vscode.postMessage({ + t: 'viewSourceFile', + loc, + databaseUri + }); + e.preventDefault(); + e.stopPropagation(); + }; +} + +/** + * Render a location as a link which when clicked displays the original location. + */ +export function renderLocation(loc: LocationValue | undefined, label: string | undefined, + databaseUri: string, title?: string): JSX.Element { + + // If the label was empty, use a placeholder instead, so the link is still clickable. + let displayLabel = label; + if (label === undefined || label === '') + displayLabel = '[empty string]'; + else if (label.match(/^\s+$/)) + displayLabel = `[whitespace: "${label}"]`; + + if (loc !== undefined) { + const resolvableLoc = tryGetResolvableLocation(loc); + if (resolvableLoc !== undefined) { + return {displayLabel}; + } else { + return {displayLabel}; + } + } + return +} + +/** + * Returns the attributes for a zebra-striped table row at position `index`. + */ +export function zebraStripe(index: number, ...otherClasses: string[]): { className: string } { + return { className: [(index % 2) ? oddRowClassName : evenRowClassName, otherClasses].join(' ') }; +} diff --git a/extensions/ql-vscode/src/view/result-tables.tsx b/extensions/ql-vscode/src/view/result-tables.tsx new file mode 100644 index 000000000..8e2a11b72 --- /dev/null +++ b/extensions/ql-vscode/src/view/result-tables.tsx @@ -0,0 +1,147 @@ +import cx from 'classnames'; +import * as React from 'react'; +import { DatabaseInfo, Interpretation, SortState } from '../interface-types'; +import { PathTable } from './alert-table'; +import { RawTable } from './raw-results-table'; +import { ResultTableProps, toggleDiagnosticsClassName, toggleDiagnosticsSelectedClassName, tableSelectionHeaderClassName } from './result-table-utils'; +import { ResultSet, vscode } from './results'; + +/** + * Properties for the `ResultTables` component. + */ +export interface ResultTablesProps { + rawResultSets: readonly ResultSet[]; + interpretation: Interpretation | undefined; + database: DatabaseInfo; + resultsPath: string | undefined; + kind: string | undefined; + sortStates: Map; + isLoadingNewResults: boolean; +} + +/** + * State for the `ResultTables` component. + */ +interface ResultTablesState { + selectedTable: string; // name of selected result set +} + +const ALERTS_TABLE_NAME = 'alerts'; +const SELECT_TABLE_NAME = '#select'; +const UPDATING_RESULTS_TEXT_CLASS_NAME = "vscode-codeql__result-tables-updating-text"; + +/** + * Displays multiple `ResultTable` tables, where the table to be displayed is selected by a + * dropdown. + */ +export class ResultTables + extends React.Component { + + private getResultSets(): ResultSet[] { + const resultSets: ResultSet[] = + this.props.rawResultSets.map(rs => ({ t: 'RawResultSet', ...rs })); + + if (this.props.interpretation != undefined) { + resultSets.push({ + t: 'SarifResultSet', + // FIXME: The values of version, columns, tupleCount are + // unused stubs because a SarifResultSet schema isn't used the + // same way as a RawResultSet. Probably should pull `name` field + // out. + schema: { name: ALERTS_TABLE_NAME, version: 0, columns: [], tupleCount: 1 }, + name: ALERTS_TABLE_NAME, + ...this.props.interpretation, + }); + } + return resultSets; + } + + constructor(props: ResultTablesProps) { + super(props); + + this.state = { + // Get the result set that should be displayed by default + selectedTable: ResultTables.getDefaultResultSet(this.getResultSets()) + }; + } + + private static getDefaultResultSet(resultSets: readonly ResultSet[]): string { + const resultSetNames = resultSets.map(resultSet => resultSet.schema.name) + // Choose first available result set from the array + return [ALERTS_TABLE_NAME, SELECT_TABLE_NAME, resultSets[0].schema.name].filter(resultSetName => resultSetNames.includes(resultSetName))[0]; + } + + private onChange = (event: React.ChangeEvent): void => { + this.setState({ selectedTable: event.target.value }); + } + + render(): React.ReactNode { + const selectedTable = this.state.selectedTable; + const resultSets = this.getResultSets(); + const { database, resultsPath, kind } = this.props; + + // Only show the Problems view display checkbox for the alerts table. + const toggleDiagnosticsClass = cx(toggleDiagnosticsClassName, { + [toggleDiagnosticsSelectedClassName]: selectedTable === ALERTS_TABLE_NAME + }); + + return
+
+ +
+ { + if (resultsPath !== undefined) { + vscode.postMessage({ + t: 'toggleDiagnostics', + resultsPath: resultsPath, + databaseUri: database.databaseUri, + visible: e.target.checked, + kind: kind + }); + } + }} /> + +
+ { + this.props.isLoadingNewResults ? + Updating results… + : null + } +
+ { + resultSets.map(resultSet => + + ) + } +
; + } +} + +class ResultTable extends React.Component { + + constructor(props: ResultTableProps) { + super(props); + } + + render(): React.ReactNode { + const { resultSet } = this.props; + switch (resultSet.t) { + case 'RawResultSet': return ; + case 'SarifResultSet': return ; + } + } +} diff --git a/extensions/ql-vscode/src/view/results.tsx b/extensions/ql-vscode/src/view/results.tsx new file mode 100644 index 000000000..2c6c73648 --- /dev/null +++ b/extensions/ql-vscode/src/view/results.tsx @@ -0,0 +1,329 @@ +import * as React from 'react'; +import * as Rdom from 'react-dom'; +import * as bqrs from 'semmle-bqrs'; +import { ElementBase, LocationValue, PrimitiveColumnValue, PrimitiveTypeKind, ResultSetSchema, tryGetResolvableLocation } from 'semmle-bqrs'; +import { assertNever } from '../helpers-pure'; +import { DatabaseInfo, FromResultsViewMsg, Interpretation, IntoResultsViewMsg, SortedResultSetInfo, SortState } from '../interface-types'; +import { ResultTables } from './result-tables'; + +/** + * results.tsx + * ----------- + * + * Displaying query results. + */ + +interface VsCodeApi { + /** + * Post message back to vscode extension. + */ + postMessage(msg: FromResultsViewMsg): void; +} +declare const acquireVsCodeApi: () => VsCodeApi; +export const vscode = acquireVsCodeApi(); + +export interface ResultElement { + label: string, + location?: LocationValue +} + +export interface ResultUri { + uri: string; +} + +export type ResultValue = ResultElement | ResultUri | string; + +export type ResultRow = ResultValue[]; + +export type RawTableResultSet = { t: 'RawResultSet' } & RawResultSet; +export type PathTableResultSet = { t: 'SarifResultSet', readonly schema: ResultSetSchema, name: string } & Interpretation; + +export type ResultSet = + | RawTableResultSet + | PathTableResultSet; + +export interface RawResultSet { + readonly schema: ResultSetSchema; + readonly rows: readonly ResultRow[]; +} + +async function* getChunkIterator(response: Response): AsyncIterableIterator { + if (!response.ok) { + throw new Error(`Failed to load results: (${response.status}) ${response.statusText}`); + } + const reader = response.body!.getReader(); + while (true) { + const { value, done } = await reader.read(); + if (done) { + return; + } + yield value; + } +} + +function translatePrimitiveValue(value: PrimitiveColumnValue, type: PrimitiveTypeKind): + ResultValue { + + switch (type) { + case 'i': + case 'f': + case 's': + case 'd': + case 'b': + return value.toString(); + + case 'u': + return { + uri: value as string + }; + } +} + +async function parseResultSets(response: Response): Promise { + const chunks = getChunkIterator(response); + + const resultSets: ResultSet[] = []; + + await bqrs.parse(chunks, (resultSetSchema) => { + const columnTypes = resultSetSchema.columns.map((column) => column.type); + const rows: ResultRow[] = []; + resultSets.push({ + t: 'RawResultSet', + schema: resultSetSchema, + rows: rows + }); + + return (tuple) => { + const row: ResultValue[] = []; + tuple.forEach((value, index) => { + const type = columnTypes[index]; + if (type.type === 'e') { + const element: ElementBase = value as ElementBase; + const label = (element.label !== undefined) ? element.label : element.id.toString(); //REVIEW: URLs? + const resolvableLocation = tryGetResolvableLocation(element.location); + if (resolvableLocation !== undefined) { + row.push({ + label: label, + location: resolvableLocation + }); + } + else { + // No location link. + row.push(label); + } + } + else { + row.push(translatePrimitiveValue(value as PrimitiveColumnValue, type.type)); + } + }); + + rows.push(row); + }; + }); + + return resultSets; +} + +interface ResultsInfo { + resultsPath: string; + kind: string | undefined; + database: DatabaseInfo; + interpretation: Interpretation | undefined; + sortedResultsMap: Map; + /** + * See {@link SetStateMsg.shouldKeepOldResultsWhileRendering}. + */ + shouldKeepOldResultsWhileRendering: boolean; +} + +interface Results { + resultSets: readonly ResultSet[]; + sortStates: Map; + database: DatabaseInfo; +} + +interface ResultsState { + // We use `null` instead of `undefined` here because in React, `undefined` is + // used to mean "did not change" when updating the state of a component. + resultsInfo: ResultsInfo | null; + results: Results | null; + errorMessage: string; +} + +interface ResultsViewState { + displayedResults: ResultsState; + nextResultsInfo: ResultsInfo | null; + isExpectingResultsUpdate: boolean; +} + +/** + * A minimal state container for displaying results. + */ +class App extends React.Component<{}, ResultsViewState> { + constructor(props: any) { + super(props); + this.state = { + displayedResults: { + resultsInfo: null, + results: null, + errorMessage: '' + }, + nextResultsInfo: null, + isExpectingResultsUpdate: true + }; + } + + handleMessage(msg: IntoResultsViewMsg): void { + switch (msg.t) { + case 'setState': + this.updateStateWithNewResultsInfo({ + resultsPath: msg.resultsPath, + kind: msg.kind, + sortedResultsMap: new Map(Object.entries(msg.sortedResultsMap)), + database: msg.database, + interpretation: msg.interpretation, + shouldKeepOldResultsWhileRendering: msg.shouldKeepOldResultsWhileRendering + }); + + this.loadResults(); + break; + case 'resultsUpdating': + this.setState({ + isExpectingResultsUpdate: true + }); + break; + default: + assertNever(msg); + } + } + + private updateStateWithNewResultsInfo(resultsInfo: ResultsInfo): void { + this.setState(prevState => { + const stateWithDisplayedResults = (displayedResults: ResultsState) => ({ + displayedResults, + isExpectingResultsUpdate: prevState.isExpectingResultsUpdate, + nextResultsInfo: resultsInfo + }); + + if (!prevState.isExpectingResultsUpdate && resultsInfo === null) { + // No results to display + return stateWithDisplayedResults({ + resultsInfo: null, + results: null, + errorMessage: 'No results to display' + }); + } + if (!resultsInfo || !resultsInfo.shouldKeepOldResultsWhileRendering) { + // Display loading message + return stateWithDisplayedResults({ + resultsInfo: null, + results: null, + errorMessage: 'Loading results…' + }); + } + return stateWithDisplayedResults(prevState.displayedResults); + }); + } + + private async loadResults(): Promise { + const resultsInfo = this.state.nextResultsInfo; + if (resultsInfo === null) { + return; + } + + let results: Results | null = null; + let statusText: string = ''; + try { + results = { + resultSets: await this.getResultSets(resultsInfo), + database: resultsInfo.database, + sortStates: this.getSortStates(resultsInfo) + }; + } + catch (e) { + let errorMessage: string; + if (e instanceof Error) { + errorMessage = e.message; + } else { + errorMessage = 'Unknown error'; + } + + statusText = `Error loading results: ${errorMessage}`; + } + + this.setState(prevState => { + // Only set state if this results info is still current. + if (resultsInfo !== prevState.nextResultsInfo) { + return null; + } + return { + displayedResults: { + resultsInfo, + results, + errorMessage: statusText + }, + nextResultsInfo: null, + isExpectingResultsUpdate: false + } + }); + } + + private async getResultSets(resultsInfo: ResultsInfo): Promise { + const unsortedResponse = await fetch(resultsInfo.resultsPath); + const unsortedResultSets = await parseResultSets(unsortedResponse); + return Promise.all(unsortedResultSets.map(async unsortedResultSet => { + const sortedResultSetInfo = resultsInfo.sortedResultsMap.get(unsortedResultSet.schema.name); + if (sortedResultSetInfo === undefined) { + return unsortedResultSet; + } + const response = await fetch(sortedResultSetInfo.resultsPath); + const resultSets = await parseResultSets(response); + if (resultSets.length != 1) { + throw new Error(`Expected sorted BQRS to contain a single result set, encountered ${resultSets.length} result sets.`); + } + return resultSets[0]; + })); + } + + private getSortStates(resultsInfo: ResultsInfo): Map { + const entries = Array.from(resultsInfo.sortedResultsMap.entries()); + return new Map(entries.map(([key, sortedResultSetInfo]) => + [key, sortedResultSetInfo.sortState])); + } + + render() { + const displayedResults = this.state.displayedResults; + if (displayedResults.results !== null) { + return ; + } + else { + return {displayedResults.errorMessage}; + } + } + + componentDidMount() { + this.vscodeMessageHandler = evt => this.handleMessage(evt.data as IntoResultsViewMsg); + window.addEventListener('message', this.vscodeMessageHandler); + } + + componentWillUnmount() { + if (this.vscodeMessageHandler) { + window.removeEventListener('message', this.vscodeMessageHandler); + } + } + + private vscodeMessageHandler: ((ev: MessageEvent) => void) | undefined = undefined; +} + +Rdom.render( + , + document.getElementById('root') +); + +vscode.postMessage({ t: "resultViewLoaded" }) \ No newline at end of file diff --git a/extensions/ql-vscode/src/view/resultsView.css b/extensions/ql-vscode/src/view/resultsView.css new file mode 100644 index 000000000..a3c266d13 --- /dev/null +++ b/extensions/ql-vscode/src/view/resultsView.css @@ -0,0 +1,140 @@ +.vscode-codeql__result-table { + display: none; + border-collapse: collapse; + width: 100%; +} + +.vscode-codeql__result-table--selected { + display: table; +} + +.vscode-codeql__table-selection-header { + display: flex; + padding: 0.5em 0; +} + +.vscode-codeql__table-selection-header select { + border: 0; +} + +.vscode-codeql__result-table-toggle-diagnostics { + display: none; + text-align: left; + margin-left: auto; +} + +.vscode-codeql__result-table-toggle-diagnostics--selected { + display: inline-block; +} + +/* Keep the checkbox and its label in horizontal alignment. */ +.vscode-codeql__result-table-toggle-diagnostics--selected label, +.vscode-codeql__result-table-toggle-diagnostics--selected input { + display: inline-block; + vertical-align: middle; +} +.vscode-codeql__result-table-toggle-diagnostics--selected input { + margin: 3px 3px 1px 3px; +} + + +.vscode-codeql__result-table th { + border-top: 1px solid rgba(88,96,105,0.25); + border-bottom: 1px solid rgba(88,96,105,0.25); + font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji; + background: rgba(225,228,232, 0.25); + padding: 0.25em 0.5em; + text-align: center; + font-weight: normal; + opacity: 0.6; +} + +.vscode-codeql__result-table .sort-none::after { + /* Want to take up the same space as the other sort directions */ + content: " ▲"; + visibility: hidden; +} + +.vscode-codeql__result-table .sort-asc::after { + content: " ▲"; +} + +.vscode-codeql__result-table .sort-desc::after { + content: " ▼"; +} + +.vscode-codeql__result-table td { + margin: 0; + padding: 0; + padding: 0.25em 0.5em; + white-space: pre-wrap; + text-align: left; +} + +.vscode-codeql__result-table-location-link { + font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; +} + +select { + background-color: var(--vscode-dropdown-background); + color: var(--vscode-dropdown-foreground); + border-color: var(--vscode-dropdown-border); + font-family: inherit; + font-size: inherit; +} + +.vscode-codeql__result-tables-updating-text { + margin-left: 1em; +} + +.vscode-codeql__result-table-row--odd { + background-color: inherit; +} + +.vscode-codeql__result-table-row--even { + background-color: var(--vscode-textBlockQuote-background); +} + +td.vscode-codeql__icon-cell { + text-align: center; + position: relative; + white-space: nowrap; + margin: 0; + padding: 0; + width: 24px; +} + +td.vscode-codeql__dropdown-cell:hover { + cursor: pointer; +} + +td.vscode-codeql__path-index-cell { + text-align: right; +} + +td.vscode-codeql__location-cell { + text-align: right; +} + +.vscode-codeql__vertical-rule { + border-left: 1px solid var(--vscode-dropdown-border); + height: 100%; + position: absolute; + top: 0; + bottom: 0; +} + +.vscode-codeql__title { + /* Something that isn't a link, but which has a title attribute */ + text-decoration-line: underline; + text-decoration-style: dotted; +} + +.octicon { + fill: var(--vscode-editor-foreground); + margin-top: .25em; +} + +.octicon-light { + opacity: 0.6; +} diff --git a/extensions/ql-vscode/src/view/tsconfig.json b/extensions/ql-vscode/src/view/tsconfig.json new file mode 100644 index 000000000..2af7d89e1 --- /dev/null +++ b/extensions/ql-vscode/src/view/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "module": "esnext", + "moduleResolution": "node", + "target": "es6", + "outDir": "out", + "lib": [ + "es6", + "dom" + ], + "jsx": "react", + "sourceMap": true, + "rootDir": "..", + "strict": true, + "noUnusedLocals": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "experimentalDecorators": true + }, + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/extensions/ql-vscode/src/vscode-tests/index-template.ts b/extensions/ql-vscode/src/vscode-tests/index-template.ts new file mode 100644 index 000000000..55f435e28 --- /dev/null +++ b/extensions/ql-vscode/src/vscode-tests/index-template.ts @@ -0,0 +1,61 @@ +import * as path from 'path'; +import * as Mocha from 'mocha'; +import * as glob from 'glob'; + +/** + * Helper function that runs all Mocha tests found in the + * given test root directory. + * + * For each integration test suite, `vscode-test` expects + * a test runner script exporting a function with the signature: + * ```ts + * export function run(): Promise + * ``` + * + * To create an integration test suite: + * - create a directory beside this file + * - create integration tests in the directory, named `.test.ts` + * - create an `index.ts` file in the directory, containing: + * ```ts + * import { runTestsInDirectory } from '../index-template'; + * export function run(): Promise { + * return runTestsInDirectory(__dirname); + * } + * ``` + * + * After https://github.com/microsoft/TypeScript/issues/420 is implemented, + * this pattern can be expressed more neatly using a module interface. + */ +export function runTestsInDirectory(testsRoot: string): Promise { + // Create the mocha test + const mocha = new Mocha({ + ui: 'bdd' + }); + mocha.useColors(true); + + return new Promise((c, e) => { + console.log(`Adding test cases from ${testsRoot}`); + glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { + if (err) { + return e(err); + } + + + // Add files to the test suite + files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); + + try { + // Run the mocha test + mocha.run(failures => { + if (failures > 0) { + e(new Error(`${failures} tests failed.`)); + } else { + c(); + } + }); + } catch (err) { + e(err); + } + }); + }); +} \ No newline at end of file diff --git a/extensions/ql-vscode/src/vscode-tests/minimal-workspace/activation.test.ts b/extensions/ql-vscode/src/vscode-tests/minimal-workspace/activation.test.ts new file mode 100644 index 000000000..45a0844f8 --- /dev/null +++ b/extensions/ql-vscode/src/vscode-tests/minimal-workspace/activation.test.ts @@ -0,0 +1,26 @@ +import * as assert from 'assert'; +import * as path from 'path'; +import * as vscode from 'vscode'; + +describe('launching with a minimal workspace', async () => { + const ext = vscode.extensions.getExtension('GitHub.vscode-codeql'); + it('should install the extension', () => { + assert(ext); + }); + it('should not activate the extension at first', () => { + assert(ext!.isActive === false); + }); + it('should activate the extension when a .ql file is opened', async function () { + const folders = vscode.workspace.workspaceFolders; + assert(folders && folders.length === 1); + const folderPath = folders![0].uri.fsPath; + const documentPath = path.resolve(folderPath, 'query.ql'); + const document = await vscode.workspace.openTextDocument(documentPath); + assert(document.languageId === 'ql'); + // Delay slightly so that the extension has time to activate. + this.timeout(3000); + setTimeout(() => { + assert(ext!.isActive); + }, 1000); + }); +}); \ No newline at end of file diff --git a/extensions/ql-vscode/src/vscode-tests/minimal-workspace/index.ts b/extensions/ql-vscode/src/vscode-tests/minimal-workspace/index.ts new file mode 100644 index 000000000..53578998d --- /dev/null +++ b/extensions/ql-vscode/src/vscode-tests/minimal-workspace/index.ts @@ -0,0 +1,4 @@ +import { runTestsInDirectory } from '../index-template'; +export function run(): Promise { + return runTestsInDirectory(__dirname); +} \ No newline at end of file diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/activation.test.ts b/extensions/ql-vscode/src/vscode-tests/no-workspace/activation.test.ts new file mode 100644 index 000000000..8145b6ac0 --- /dev/null +++ b/extensions/ql-vscode/src/vscode-tests/no-workspace/activation.test.ts @@ -0,0 +1,13 @@ +import * as assert from 'assert'; +import * as vscode from 'vscode'; + +// Note that this may open the most recent VSCode workspace. +describe('launching with no specified workspace', () => { + const ext = vscode.extensions.getExtension('GitHub.vscode-codeql'); + it('should install the extension', () => { + assert(ext); + }); + it('should not activate the extension at first', () => { + assert(ext!.isActive === false); + }); +}); \ No newline at end of file diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/archive-filesystem-provider.test.ts b/extensions/ql-vscode/src/vscode-tests/no-workspace/archive-filesystem-provider.test.ts new file mode 100644 index 000000000..b7252acb1 --- /dev/null +++ b/extensions/ql-vscode/src/vscode-tests/no-workspace/archive-filesystem-provider.test.ts @@ -0,0 +1,38 @@ +import { expect } from "chai"; +import * as path from "path"; +import { ArchiveFileSystemProvider, decodeSourceArchiveUri, encodeSourceArchiveUri, ZipFileReference } from "../../archive-filesystem-provider"; + +describe("archive filesystem provider", () => { + it("reads empty file correctly", async () => { + const archiveProvider = new ArchiveFileSystemProvider(); + const uri = encodeSourceArchiveUri({ + sourceArchiveZipPath: path.resolve(__dirname, "data/archive-filesystem-provider-test/single_file.zip"), + pathWithinSourceArchive: "/aFileName.txt" + }); + const data = await archiveProvider.readFile(uri); + expect(data.length).to.equal(0); + }); +}); + +describe('source archive uri encoding', function () { + const testCases: { name: string, input: ZipFileReference }[] = [ + { + name: 'mixed case and unicode', + input: { sourceArchiveZipPath: "/I-\u2665-codeql.zip", pathWithinSourceArchive: "/foo/bar" } + }, + { + name: 'Windows path', + input: { sourceArchiveZipPath: "C:/Users/My Name/folder/src.zip", pathWithinSourceArchive: "/foo/bar.ext" } + }, + { + name: 'Unix path', + input: { sourceArchiveZipPath: "/home/folder/src.zip", pathWithinSourceArchive: "/foo/bar.ext" } + } + ]; + for (const testCase of testCases) { + it(`should work round trip with ${testCase.name}`, function () { + const output = decodeSourceArchiveUri(encodeSourceArchiveUri(testCase.input)); + expect(output).to.eql(testCase.input); + }); + } +}); diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/cli-version.test.ts b/extensions/ql-vscode/src/vscode-tests/no-workspace/cli-version.test.ts new file mode 100644 index 000000000..17e2ac1c3 --- /dev/null +++ b/extensions/ql-vscode/src/vscode-tests/no-workspace/cli-version.test.ts @@ -0,0 +1,50 @@ +import { expect } from "chai"; +import "mocha"; +import { tryParseVersionString } from "../../cli-version"; + +describe("Version parsing", () => { + it("should accept version without prerelease and build metadata", () => { + const v = tryParseVersionString("3.2.4")!; + expect(v.majorVersion).to.equal(3); + expect(v.minorVersion).to.equal(2); + expect(v.patchVersion).to.equal(4); + expect(v.prereleaseVersion).to.be.undefined; + expect(v.buildMetadata).to.be.undefined; + }); + + it("should accept v at the beginning of the version", () => { + const v = tryParseVersionString("v3.2.4")!; + expect(v.majorVersion).to.equal(3); + expect(v.minorVersion).to.equal(2); + expect(v.patchVersion).to.equal(4); + expect(v.prereleaseVersion).to.be.undefined; + expect(v.buildMetadata).to.be.undefined; + }); + + it("should accept version with prerelease", () => { + const v = tryParseVersionString("v3.2.4-alpha.0")!; + expect(v.majorVersion).to.equal(3); + expect(v.minorVersion).to.equal(2); + expect(v.patchVersion).to.equal(4); + expect(v.prereleaseVersion).to.equal("alpha.0"); + expect(v.buildMetadata).to.be.undefined; + }); + + it("should accept version with prerelease and build metadata", () => { + const v = tryParseVersionString("v3.2.4-alpha.0+abcdef0")!; + expect(v.majorVersion).to.equal(3); + expect(v.minorVersion).to.equal(2); + expect(v.patchVersion).to.equal(4); + expect(v.prereleaseVersion).to.equal("alpha.0"); + expect(v.buildMetadata).to.equal("abcdef0"); + }); + + it("should accept version with build metadata", () => { + const v = tryParseVersionString("v3.2.4+abcdef0")!; + expect(v.majorVersion).to.equal(3); + expect(v.minorVersion).to.equal(2); + expect(v.patchVersion).to.equal(4); + expect(v.prereleaseVersion).to.be.undefined; + expect(v.buildMetadata).to.equal("abcdef0"); + }); +}); diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/archive-filesystem-provider-test/single_file.zip b/extensions/ql-vscode/src/vscode-tests/no-workspace/data/archive-filesystem-provider-test/single_file.zip new file mode 100644 index 0000000000000000000000000000000000000000..3a3ceb15a156feadeacd7a82c70766c6bb3f25d5 GIT binary patch literal 394 zcmWIWW@Zs#0D-y9{{CPFl;B}dU??t1j!!H~&d4lF)ejBfWnka+bZIOMmsW5yFtU7Q zWME(cX$b(E#KFM9Fd5AxNi>rZ-7<4h{StFi^-3yAz{V~E8VkZ8qtT3I1{%!H0kVJz zL;yj6HzSh>Gj2yiR538Tbp)|U^8& { + const owner = "someowner"; + const repo = "somerepo"; + const sampleReleaseResponse: GithubRelease[] = [ + { + "assets": [], + "created_at": "2019-09-01T00:00:00Z", + "id": 1, + "name": "", + "prerelease": false, + "tag_name": "v2.1.0" + }, + { + "assets": [], + "created_at": "2019-08-10T00:00:00Z", + "id": 2, + "name": "", + "prerelease": false, + "tag_name": "v3.1.1" + }, + { + "assets": [], + "created_at": "2019-09-05T00:00:00Z", + "id": 3, + "name": "", + "prerelease": false, + "tag_name": "v2.0.0" + }, + { + "assets": [], + "created_at": "2019-08-11T00:00:00Z", + "id": 4, + "name": "", + "prerelease": true, + "tag_name": "v3.1.2-pre" + }, + ]; + const unconstrainedVersionConstraint = { + description: "*", + isVersionCompatible: () => true + }; + + it("picking latest release: is based on version", async () => { + class MockReleasesApiConsumer extends ReleasesApiConsumer { + protected async makeApiCall(apiPath: string, additionalHeaders: { [key: string]: string } = {}): Promise { + if (apiPath === `/repos/${owner}/${repo}/releases`) { + return Promise.resolve(new fetch.Response(JSON.stringify(sampleReleaseResponse))); + } + return Promise.reject(new Error(`Unknown API path: ${apiPath}`)); + } + } + + const consumer = new MockReleasesApiConsumer(owner, repo); + + const latestRelease = await consumer.getLatestRelease(unconstrainedVersionConstraint); + expect(latestRelease.id).to.equal(2); + }); + + it("picking latest release: obeys version constraints", async () => { + class MockReleasesApiConsumer extends ReleasesApiConsumer { + protected async makeApiCall(apiPath: string, additionalHeaders: { [key: string]: string } = {}): Promise { + if (apiPath === `/repos/${owner}/${repo}/releases`) { + return Promise.resolve(new fetch.Response(JSON.stringify(sampleReleaseResponse))); + } + return Promise.reject(new Error(`Unknown API path: ${apiPath}`)); + } + } + + const consumer = new MockReleasesApiConsumer(owner, repo); + + const latestRelease = await consumer.getLatestRelease({ + description: "2.*.*", + isVersionCompatible: version => version.majorVersion === 2 + }); + expect(latestRelease.id).to.equal(1); + }); + + it("picking latest release: includes prereleases when option set", async () => { + class MockReleasesApiConsumer extends ReleasesApiConsumer { + protected async makeApiCall(apiPath: string, additionalHeaders: { [key: string]: string } = {}): Promise { + if (apiPath === `/repos/${owner}/${repo}/releases`) { + return Promise.resolve(new fetch.Response(JSON.stringify(sampleReleaseResponse))); + } + return Promise.reject(new Error(`Unknown API path: ${apiPath}`)); + } + } + + const consumer = new MockReleasesApiConsumer(owner, repo); + + const latestRelease = await consumer.getLatestRelease(unconstrainedVersionConstraint, true); + expect(latestRelease.id).to.equal(4); + }); + + it("gets correct assets for a release", async () => { + const expectedAssets: GithubReleaseAsset[] = [ + { + "id": 1, + "name": "firstAsset", + "size": 11 + }, + { + "id": 2, + "name": "secondAsset", + "size": 12 + } + ]; + + class MockReleasesApiConsumer extends ReleasesApiConsumer { + protected async makeApiCall(apiPath: string, additionalHeaders: { [key: string]: string } = {}): Promise { + if (apiPath === `/repos/${owner}/${repo}/releases`) { + const responseBody: GithubRelease[] = [{ + "assets": expectedAssets, + "created_at": "2019-09-01T00:00:00Z", + "id": 1, + "name": "Release 1", + "prerelease": false, + "tag_name": "v2.0.0" + }]; + + return Promise.resolve(new fetch.Response(JSON.stringify(responseBody))); + } + return Promise.reject(new Error(`Unknown API path: ${apiPath}`)); + } + } + + const consumer = new MockReleasesApiConsumer(owner, repo); + + const assets = (await consumer.getLatestRelease(unconstrainedVersionConstraint)).assets; + + expect(assets.length).to.equal(expectedAssets.length); + expectedAssets.map((expectedAsset, index) => { + expect(assets[index].id).to.equal(expectedAsset.id); + expect(assets[index].name).to.equal(expectedAsset.name); + expect(assets[index].size).to.equal(expectedAsset.size); + }); + }); +}); + +describe("Release version ordering", () => { + function createVersion(majorVersion: number, minorVersion: number, patchVersion: number, prereleaseVersion?: string, buildMetadata?: string): Version { + return { + buildMetadata, + majorVersion, + minorVersion, + patchVersion, + prereleaseVersion, + rawString: `${majorVersion}.${minorVersion}.${patchVersion}` + + prereleaseVersion ? `-${prereleaseVersion}` : "" + + buildMetadata ? `+${buildMetadata}` : "" + }; + } + + it("major versions compare correctly", () => { + expect(versionCompare(createVersion(3, 0, 0), createVersion(2, 9, 9)) > 0).to.be.true; + }); + + it("minor versions compare correctly", () => { + expect(versionCompare(createVersion(2, 1, 0), createVersion(2, 0, 9)) > 0).to.be.true; + }); + + it("patch versions compare correctly", () => { + expect(versionCompare(createVersion(2, 1, 2), createVersion(2, 1, 1)) > 0).to.be.true; + }); + + it("prerelease versions compare correctly", () => { + expect(versionCompare(createVersion(2, 1, 0, "alpha.2"), createVersion(2, 1, 0, "alpha.1")) > 0).to.true; + }); + + it("build metadata compares correctly", () => { + expect(versionCompare(createVersion(2, 1, 0, "alpha.1", "abcdef0"), createVersion(2, 1, 0, "alpha.1", "bcdef01"))).to.equal(0); + }); +}); diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/index.ts b/extensions/ql-vscode/src/vscode-tests/no-workspace/index.ts new file mode 100644 index 000000000..53578998d --- /dev/null +++ b/extensions/ql-vscode/src/vscode-tests/no-workspace/index.ts @@ -0,0 +1,4 @@ +import { runTestsInDirectory } from '../index-template'; +export function run(): Promise { + return runTestsInDirectory(__dirname); +} \ No newline at end of file diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/webview-uri.test.ts b/extensions/ql-vscode/src/vscode-tests/no-workspace/webview-uri.test.ts new file mode 100644 index 000000000..37c6930b1 --- /dev/null +++ b/extensions/ql-vscode/src/vscode-tests/no-workspace/webview-uri.test.ts @@ -0,0 +1,34 @@ +import { expect } from "chai"; +import * as tmp from "tmp"; +import { window, ViewColumn, Uri } from "vscode"; +import { fileUriToWebviewUri, webviewUriToFileUri } from '../../interface'; + +describe('webview uri conversion', function () { + it('should correctly round trip from filesystem to webview and back', function () { + const tmpFile = tmp.fileSync({ prefix: 'uri_test_', postfix: '.bqrs', keep: false }); + const fileUriOnDisk = Uri.file(tmpFile.name); + const panel = window.createWebviewPanel( + 'test panel', + 'test panel', + ViewColumn.Beside, + { + enableScripts: false, + localResourceRoots: [ + fileUriOnDisk + ] + } + ); + after(function () { + panel.dispose(); + tmpFile.removeCallback(); + }); + + // CSP allowing nothing, to prevent warnings. + const html = ``; + panel.webview.html = html; + + const webviewUri = fileUriToWebviewUri(panel, fileUriOnDisk); + const reconstructedFileUri = webviewUriToFileUri(webviewUri); + expect(reconstructedFileUri.toString(true)).to.equal(fileUriOnDisk.toString(true)); + }); +}); diff --git a/extensions/ql-vscode/src/vscode-tests/run-integration-tests.ts b/extensions/ql-vscode/src/vscode-tests/run-integration-tests.ts new file mode 100644 index 000000000..03c84d009 --- /dev/null +++ b/extensions/ql-vscode/src/vscode-tests/run-integration-tests.ts @@ -0,0 +1,44 @@ +import * as path from 'path'; +import { runTests } from 'vscode-test'; + +/** + * Integration test runner. Launches the VSCode Extension Development Host with this extension installed. + * See https://github.com/microsoft/vscode-test/blob/master/sample/test/runTest.ts + */ +async function main() { + try { + // The folder containing the Extension Manifest package.json + // Passed to `--extensionDevelopmentPath`. + const extensionDevelopmentPath = path.resolve(__dirname, '../..'); + + // List of integration test suites. + // The path to the extension test runner script is passed to --extensionTestsPath. + const integrationTestSuites = [ + // Tests with no workspace selected upon launch. + { + extensionDevelopmentPath: extensionDevelopmentPath, + extensionTestsPath: path.resolve(__dirname, 'no-workspace', 'index'), + launchArgs: ['--disable-extensions'], + }, + // Tests with a simple workspace selected upon launch. + { + extensionDevelopmentPath: extensionDevelopmentPath, + extensionTestsPath: path.resolve(__dirname, 'minimal-workspace', 'index'), + launchArgs: [ + path.resolve(__dirname, '../../test/data'), + '--disable-extensions', + ] + } + ]; + + for (const integrationTestSuite of integrationTestSuites) { + // Download and unzip VS Code if necessary, and run the integration test suite. + await runTests(integrationTestSuite); + } + } catch (err) { + console.error('Failed to run tests'); + process.exit(1); + } +} + +main(); diff --git a/extensions/ql-vscode/syntaxes/dbscheme.tmLanguage.yml b/extensions/ql-vscode/syntaxes/dbscheme.tmLanguage.yml new file mode 100644 index 000000000..3fd3c08f1 --- /dev/null +++ b/extensions/ql-vscode/syntaxes/dbscheme.tmLanguage.yml @@ -0,0 +1,353 @@ +--- +# This file is transformed into the equivalent JSON TextMate grammar, with the following additional +# features available: +# +# The `regexOptions` Property +# A top-level property named `regexOptions` may be defined with a string value. This string +# represents the set of regular expression options to apply to all regular expressions throughout +# the file. +# +# Macros +# The `macros` element defines a map of macro names to replacement text. When a `match`, `begin`, or +# `end` property has a value that is a single-key map, the value is replaced with the value of the +# macro named by the key, with any use of `(?#)` in the macro text replaced with the text of the +# value of the key, surrounded by a non-capturing group (`(?:)`). For example: +# +# The `beginPattern` and `endPattern` Properties +# A rule can have a `beginPattern` or `endPattern` property whose value is a reference to another +# rule (e.g. `#other-rule`). The `beginPattern` property is replaced as follows: +# +# my-rule: +# beginPattern: '#other-rule' +# +# would be transformed to +# +# my-rule: +# begin: '(?#other-rule)' +# beginCaptures: +# '0': +# patterns: +# - include: '#other-rule' +# +# An `endPattern` property is transformed similary. +# +# macros: +# repeat: '(?#)*' +# repository: +# multi-letter: +# match: +# repeat: '[A-Za-z]' +# name: scope.multi-letter +# +# would be transformed to +# +# repository: +# multi-letter: +# match: '(?:[A-Za-z])*' +# name: scope.multi-letter +# +# Reference Expansion +# Any comment of the form `(?#ref-id)` in a `match`, `begin`, or `end` property will be replaced +# with the match text of the rule named "ref-id". If the rule named "ref-id" consists of just a +# `patterns` property with a list of `include` directives, the replacement pattern is the +# disjunction of the match patterns of all of the included rules. + +name: DBScheme +scopeName: source.dbscheme +fileTypes: [dbscheme] +uuid: BE08929D-CEAC-4B88-9844-57475F4E8A82 +regexOptions: 'x' # Ignore pattern whitespace + +# Macros are parameterized patterns that can be used as a match elsewhere in the file. +# To use a macro, replace the string for a `match`, `begin`, or `end` property with a single-element +# map whose key is the name of the macro to invoke, and whose value is a string to be substituted for +# any usage of `(?#)` in the macro pattern definition. +macros: + keyword: '\b(?#)(?#end-of-id)' + annotation: '\#(?#)(?#end-of-id)' + +patterns: +- include: '#table-column-list' +- include: '#case-declaration-head' +- include: '#annotation' +- include: '#non-context-sensitive' +- include: '#table-name' +- include: '#type-name' + +repository: + # A character that can appear somewhere in an identifier. + id-letter: + match: '[0-9A-Za-z_]' + + # Matches a position containing a non-identifier character. Used to ensure we do not match partial + # identifiers/keywords in other rules. + end-of-id: + match: '(?!(?#id-letter))' + + id: + match: '\b [A-Za-z][0-9A-Za-z_]* (?#end-of-id)' + + at-id: + match: '@[A-Za-z][0-9A-Za-z_]* (?#end-of-id)' + + # An integer literal. + integer-literal: + match: '[0-9]+(?![0-9])' + name: constant.numeric.decimal.dbscheme + + # A pattern that can start a comment. + comment-start: + match: '// | /\*' + + # A QL comment, regardless of form. + comment: + patterns: + # A QLDoc (`/** */`) comment. + - begin: '/\*\*' + end: '\*/' + name: comment.block.documentation.dbscheme + # Highlight tag names within the QLDoc. + patterns: + - begin: '(?<=/\*\*)([^*]|\*(?!/))*$' + while: '(^|\G)\s*\*(?!/)(?=([^*]|[*](?!/))*$)' + patterns: + - include: 'text.html.markdown#fenced_code_block' + - include: 'text.html.markdown#lists' + - include: 'text.html.markdown#inline' + - match: '\G\s* (@\S+)' + name: keyword.tag.dbscheme + # A block (`/* */`) comment. + - begin: '/\*' + end: '\*/' + name: comment.block.dbscheme + # A single line (`//`) comment. + - match: //.*$ + name: comment.line.double-slash.dbscheme + +# Operators and punctuation + sub: + match: '<:' + name: punctuation.sub.sub.dbscheme + + pipe: + match: '\|' + name: punctuation.separator.pipe.dbscheme + + open-paren: + match: '\(' + name: punctuation.parenthesis.open.dbscheme + + close-paren: + match: '\)' + name: punctuation.parenthesis.close.dbscheme + + semicolon: + match: ';' + name: punctuation.separator.statement.dbscheme + + colon: + match: ':' + name: punctuation.separator.colon.dbscheme + + comma: + match: ',' + name: punctuation.separator.comma.dbscheme + + equals: + match: '=' + name: punctuation.separator.equals.dbscheme + + dot: + match: '\.' + name: punctuation.accessor.dbscheme + + open-bracket: + match: '\[' + name: punctuation.squarebracket.open.dbscheme + + close-bracket: + match: '\]' + name: punctuation.squarebracket.close.dbscheme + + operator-or-punctuation: + patterns: + - include: '#sub' + - include: '#pipe' + - include: '#open-paren' + - include: '#close-paren' + - include: '#semicolon' + - include: '#colon' + - include: '#comma' + - include: '#equals' + - include: '#dot' + - include: '#open-bracket' + - include: '#close-bracket' + +# Annotations + keyset: + match: + annotation: 'keyset' + name: storage.modifier.keyset.dbscheme + + computed: + match: + annotation: 'computed' + name: storage.modifier.computed.dbscheme + + annotation-keyword: + patterns: + - include: '#keyset' + - include: '#computed' + +# Keywords + type: + match: + keyword: 'type' + name: keyword.other.type.dbscheme + + subtype: + match: + keyword: 'subtype' + name: keyword.other.subtype.dbscheme + + case: + match: + keyword: 'case' + name: keyword.other.case.dbscheme + + of: + match: + keyword: 'of' + name: keyword.other.of.dbscheme + + order: + match: + keyword: 'order' + name: keyword.other.order.dbscheme + + key: + match: + keyword: 'key' + name: keyword.other.key.dbscheme + + ref: + match: + keyword: 'ref' + name: storage.modifier.ref.dbscheme + + int: + match: + keyword: 'int' + name: keyword.type.boolean.dbscheme + + float: + match: + keyword: 'float' + name: keyword.type.float.dbscheme + + boolean: + match: + keyword: 'boolean' + name: keyword.type.boolean.dbscheme + + date: + match: + keyword: 'date' + name: keyword.type.date.dbscheme + + varchar: + match: + keyword: 'varchar' + name: keyword.type.varchar.dbscheme + + string: + match: + keyword: 'string' + name: keyword.type.string.dbscheme + + unique: + match: + keyword: 'unique' + name: storage.modifier.unique.dbscheme + + # Any "true" keyword (not including annotations). + keyword: + patterns: + - include: '#type' + - include: '#subtype' + - include: '#case' + - include: '#of' + - include: '#order' + - include: '#key' + - include: '#ref' + - include: '#int' + - include: '#float' + - include: '#boolean' + - include: '#date' + - include: '#varchar' + - include: '#string' + - include: '#unique' + + # All tokens that can appear in any context. + non-context-sensitive: + patterns: + - include: '#comment' + - include: '#integer-literal' + - include: '#operator-or-punctuation' + - include: '#keyword' + + # An annotation on a table declaration. + annotation: + patterns: + - include: '#keyset-annotation' + - include: '#annotation-keyword' + + # A `#keyset` annotation, including its arguments. + keyset-annotation: + beginPattern: '#keyset' + # Ends after the next `]`, or when we encounter something other than a `[`. + end: '(?! \s | (?#comment-start) | \[ ) | + (?<=\])' + name: meta.block.keyset-annotation.dbscheme + patterns: + - include: '#keyset-annotation-body' + - include: '#non-context-sensitive' + + # The bracket-enclosed body of a `#keyset` annotation. + keyset-annotation-body: + beginPattern: '#open-bracket' + endPattern: '#close-bracket' + name: meta.block.keyset-annotation-body.dbscheme + patterns: + - include: '#non-context-sensitive' + - include: '#column-name' + + table-column-list: + beginPattern: '#open-paren' + endPattern: '#close-paren' + name: meta.block.table-column-list.dbscheme + patterns: + - include: '#non-context-sensitive' + - include: '#column-name' + - include: '#type-name' + + case-declaration-head: + beginPattern: '#case' + end: '(?!\s|(?#id)|(?#at-id)|(?#dot)|(?#comment-start))' + name: meta.block.case-declaration-head.dbscheme + patterns: + - include: '#non-context-sensitive' + - include: '#column-name' + - include: '#type-name' + + column-name: + match: '(?#id)' + name: entity.name.variable.parameter.dbscheme + + type-name: + match: '(?#at-id)' + name: entity.name.type.dbscheme + + table-name: + match: '(?#id)' + name: entity.name.function.dbscheme diff --git a/extensions/ql-vscode/syntaxes/ql.tmLanguage.yml b/extensions/ql-vscode/syntaxes/ql.tmLanguage.yml new file mode 100644 index 000000000..aae6481e0 --- /dev/null +++ b/extensions/ql-vscode/syntaxes/ql.tmLanguage.yml @@ -0,0 +1,1006 @@ +--- +# This file is transformed into the equivalent JSON TextMate grammar, with the following additional +# features available: +# +# The `regexOptions` Property +# A top-level property named `regexOptions` may be defined with a string value. This string +# represents the set of regular expression options to apply to all regular expressions throughout +# the file. +# +# Macros +# The `macros` element defines a map of macro names to replacement text. When a `match`, `begin`, or +# `end` property has a value that is a single-key map, the value is replaced with the value of the +# macro named by the key, with any use of `(?#)` in the macro text replaced with the text of the +# value of the key, surrounded by a non-capturing group (`(?:)`). For example: +# +# The `beginPattern` and `endPattern` Properties +# A rule can have a `beginPattern` or `endPattern` property whose value is a reference to another +# rule (e.g. `#other-rule`). The `beginPattern` property is replaced as follows: +# +# my-rule: +# beginPattern: '#other-rule' +# +# would be transformed to +# +# my-rule: +# begin: '(?#other-rule)' +# beginCaptures: +# '0': +# patterns: +# - include: '#other-rule' +# +# An `endPattern` property is transformed similary. +# +# macros: +# repeat: '(?#)*' +# repository: +# multi-letter: +# match: +# repeat: '[A-Za-z]' +# name: scope.multi-letter +# +# would be transformed to +# +# repository: +# multi-letter: +# match: '(?:[A-Za-z])*' +# name: scope.multi-letter +# +# Reference Expansion +# Any comment of the form `(?#ref-id)` in a `match`, `begin`, or `end` property will be replaced +# with the match text of the rule named "ref-id". If the rule named "ref-id" consists of just a +# `patterns` property with a list of `include` directives, the replacement pattern is the +# disjunction of the match patterns of all of the included rules. + +name: QL +scopeName: source.ql +fileTypes: [ql, qll] +uuid: 7F6926BF-1C6C-468A-A7AA-215EBAC86A4E +regexOptions: 'x' # Ignore pattern whitespace + +# Macros are parameterized patterns that can be used as a match elsewhere in the file. +# To use a macro, replace the string for a `match`, `begin`, or `end` property with a single-element +# map whose key is the name of the macro to invoke, and whose value is a string to be substituted for +# any usage of `(?#)` in the macro pattern definition. +macros: + keyword: '\b(?#)(?#end-of-id)' + +patterns: +- include: '#module-member' + +repository: + # A character that can appear somewhere in an identifier. + id-character: + match: '[0-9A-Za-z_]' + + # Matches a position containing a non-identifier character. Used to ensure we do not match partial + # identifiers/keywords in other rules. + end-of-id: + match: '(?!(?#id-character))' + + # A QL "simple identifier", which can begin with either an uppercase or a lowercase letter. + simple-id: + match: '\b [A-Za-z][0-9A-Za-z_]* (?#end-of-id)' + + # An identifier beginning with a lowercase letter. + lower-id: + match: '\b [a-z][0-9A-Za-z_]* (?#end-of-id)' + + # An identifier beginning with an uppercase letter. + upper-id: + match: '\b [A-Z][0-9A-Za-z_]* (?#end-of-id)' + + # An identifier beginning with an `@` followed by a lowercase letter. Used to represent database + # types. + at-lower-id: + match: '@[a-z][0-9A-Za-z_]* (?#end-of-id)' + + # A pattern that can start a comment. + comment-start: + match: '// | /\*' + + # All tokens that can appear in any context. + non-context-sensitive: + patterns: + - include: '#comment' + - include: '#literal' + - include: '#operator-or-punctuation' + - include: '#keyword' + +# Operators and punctuation + relational-operator: + match: '<=|<|>=|>' + name: keyword.operator.relational.ql + + comparison-operator: + match: '=|\!-' + name: keyword.operator.comparison.ql + + arithmetic-operator: + match: '\+|-|\*|/|%' + name: keyword.operator.arithmetic.ql + + comma: + match: ',' + name: punctuation.separator.comma.ql + + semicolon: + match: ';' + name: punctuation.separator.statement.ql + + dot: + match: '\.' + name: punctuation.accessor.ql + + dotdot: + match: '\.\.' + name: punctuation.operator.range.ql + + pipe: + match: '\|' + name: punctuation.separator.pipe.ql + + open-paren: + match: '\(' + name: punctuation.parenthesis.open.ql + + close-paren: + match: '\)' + name: punctuation.parenthesis.close.ql + + open-brace: + match: '\{' + name: punctuation.curlybrace.open.ql + + close-brace: + match: '\}' + name: punctuation.curlybrace.close.ql + + open-bracket: + match: '\[' + name: punctuation.squarebracket.open.ql + + close-bracket: + match: '\]' + name: punctuation.squarebracket.close.ql + + operator-or-punctuation: + patterns: + - include: '#relational-operator' + - include: '#comparison-operator' + - include: '#arithmetic-operator' + - include: '#comma' + - include: '#semicolon' + - include: '#dot' + - include: '#dotdot' + - include: '#pipe' + - include: '#open-paren' + - include: '#close-paren' + - include: '#open-brace' + - include: '#close-brace' + - include: '#open-bracket' + - include: '#close-bracket' + +# Keywords + dont-care: + match: + keyword: '_' + name: variable.language.dont-care.ql + + and: + match: + keyword: 'and' + name: keyword.other.and.ql + + any: + match: + keyword: 'any' + name: keyword.quantifier.any.ql + + as: + match: + keyword: 'as' + name: keyword.other.as.ql + + asc: + match: + keyword: 'asc' + name: keyword.order.asc.ql + + avg: + match: + keyword: 'avg' + name: keyword.aggregate.avg.ql + + boolean: + match: + keyword: 'boolean' + name: keyword.type.boolean.ql + + by: + match: + keyword: 'by' + name: keyword.order.by.ql + + class: + match: + keyword: 'class' + name: keyword.other.class.ql + + concat: + match: + keyword: 'concat' + name: keyword.aggregate.concat.ql + + count: + match: + keyword: 'count' + name: keyword.aggregate.count.ql + + date: + match: + keyword: 'date' + name: keyword.type.date.ql + + desc: + match: + keyword: 'desc' + name: keyword.order.desc.ql + + else: + match: + keyword: 'else' + name: keyword.other.else.ql + + exists: + match: + keyword: 'exists' + name: keyword.quantifier.exists.ql + + extends: + match: + keyword: 'extends' + name: keyword.other.extends.ql + + false: + match: + keyword: 'false' + name: constant.language.boolean.false.ql + + float: + match: + keyword: 'float' + name: keyword.type.float.ql + + forall: + match: + keyword: 'forall' + name: keyword.quantifier.forall.ql + + forex: + match: + keyword: 'forex' + name: keyword.quantifier.forex.ql + + from: + match: + keyword: 'from' + name: keyword.other.from.ql + + if: + match: + keyword: 'if' + name: keyword.other.if.ql + + implies: + match: + keyword: 'implies' + name: keyword.other.implies.ql + + import: + match: + keyword: 'import' + name: keyword.other.import.ql + + in: + match: + keyword: 'in' + name: keyword.other.in.ql + + instanceof: + match: + keyword: 'instanceof' + name: keyword.other.instanceof.ql + + int: + match: + keyword: 'int' + name: keyword.type.int.ql + + max: + match: + keyword: 'max' + name: keyword.aggregate.max.ql + + min: + match: + keyword: 'min' + name: keyword.aggregate.min.ql + + module: + match: + keyword: 'module' + name: keyword.other.module.ql + + newtype: + match: + keyword: 'newtype' + name: keyword.other.newtype.ql + + none: + match: + keyword: 'none' + name: keyword.quantifier.none.ql + + not: + match: + keyword: 'not' + name: keyword.other.not.ql + + or: + match: + keyword: 'or' + name: keyword.other.or.ql + + order: + match: + keyword: 'order' + name: keyword.order.order.ql + + predicate: + match: + keyword: 'predicate' + name: keyword.other.predicate.ql + + rank: + match: + keyword: 'rank' + name: keyword.aggregate.rank.ql + + result: + match: + keyword: 'result' + name: variable.language.result.ql + + select: + match: + keyword: 'select' + name: keyword.query.select.ql + + strictconcat: + match: + keyword: 'strictconcat' + name: keyword.aggregate.strictconcat.ql + + strictcount: + match: + keyword: 'strictcount' + name: keyword.aggregate.strictcount.ql + + strictsum: + match: + keyword: 'strictsum' + name: keyword.aggregate.strictsum.ql + + string: + match: + keyword: 'string' + name: keyword.type.string.ql + + sum: + match: + keyword: 'sum' + name: keyword.aggregate.sum.ql + + super: + match: + keyword: 'super' + name: variable.language.super.ql + + then: + match: + keyword: 'then' + name: keyword.other.then.ql + + this: + match: + keyword: 'this' + name: variable.language.this.ql + + true: + match: + keyword: 'true' + name: constant.language.boolean.true.ql + + where: + match: + keyword: 'where' + name: keyword.query.where.ql + + # Any "true" keyword (not including annotations). + keyword: + patterns: + - include: '#dont-care' + - include: '#and' + - include: '#any' + - include: '#as' + - include: '#asc' + - include: '#avg' + - include: '#boolean' + - include: '#by' + - include: '#class' + - include: '#concat' + - include: '#count' + - include: '#date' + - include: '#desc' + - include: '#else' + - include: '#exists' + - include: '#extends' + - include: '#false' + - include: '#float' + - include: '#forall' + - include: '#forex' + - include: '#from' + - include: '#if' + - include: '#implies' + - include: '#import' + - include: '#in' + - include: '#instanceof' + - include: '#int' + - include: '#max' + - include: '#min' + - include: '#module' + - include: '#newtype' + - include: '#none' + - include: '#not' + - include: '#or' + - include: '#order' + - include: '#predicate' + - include: '#rank' + - include: '#result' + - include: '#select' + - include: '#strictconcat' + - include: '#strictcount' + - include: '#strictsum' + - include: '#string' + - include: '#sum' + - include: '#super' + - include: '#then' + - include: '#this' + - include: '#true' + - include: '#where' + + # A keyword that can be the first token of a predicate declaration. + predicate-start-keyword: + patterns: + - include: '#boolean' + - include: '#date' + - include: '#float' + - include: '#int' + - include: '#predicate' + - include: '#string' + +# Annotation keywords + abstract: + match: + keyword: 'abstract' + name: storage.modifier.abstract.ql + + bindingset: + match: + keyword: 'bindingset' + name: storage.modifier.bindingset.ql + + cached: + match: + keyword: 'cached' + name: storage.modifier.cached.ql + + deprecated: + match: + keyword: 'deprecated' + name: storage.modifier.deprecated.ql + + external: + match: + keyword: 'external' + name: storage.modifier.external.ql + + final: + match: + keyword: 'final' + name: storage.modifier.final.ql + + language: + match: + keyword: 'language' + name: storage.modifier.language.ql + + library: + match: + keyword: 'library' + name: storage.modifier.library.ql + + override: + match: + keyword: 'override' + name: storage.modifier.override.ql + + pragma: + match: + keyword: 'pragma' + name: storage.modifier.pragma.ql + + private: + match: + keyword: 'private' + name: storage.modifier.private.ql + + query: + match: + keyword: 'query' + name: storage.modifier.query.ql + + transient: + match: + keyword: 'transient' + name: storage.modifier.transient.ql + + annotation-keyword: + patterns: + - include: '#abstract' + - include: '#bindingset' + - include: '#cached' + - include: '#deprecated' + - include: '#external' + - include: '#final' + - include: '#language' + - include: '#library' + - include: '#override' + - include: '#pragma' + - include: '#private' + - include: '#query' + - include: '#transient' + + # A QL comment, regardless of form. + comment: + patterns: + # A QLDoc (`/** */`) comment. + - begin: '/\*\*' + end: '\*/' + name: comment.block.documentation.ql + # Highlight tag names within the QLDoc. + patterns: + - begin: '(?<=/\*\*)([^*]|\*(?!/))*$' + while: '(^|\G)\s*([^*]|\*(?!/))(?=([^*]|[*](?!/))*$)' + patterns: + - include: 'text.html.markdown#fenced_code_block' + - include: 'text.html.markdown#lists' + - include: 'text.html.markdown#inline' + - match: '\G\s* (@\S+)' + name: keyword.tag.ql + # A block (`/* */`) comment. + - begin: '/\*' + end: '\*/' + name: comment.block.ql + # A single line (`//`) comment. + - match: //.*$ + name: comment.line.double-slash.ql + + # List of rules that can be matched within the body of a module, including at file scope. + module-member: + patterns: + - include: '#import-directive' + - include: '#import-as-clause' + - include: '#module-declaration' + - include: '#class-declaration' + - include: '#select-clause' + - include: '#predicate-or-field-declaration' + - include: '#non-context-sensitive' + - include: '#annotation' + + # An `import` directive. Note that we parse the optional `as` clause as a separate top-level + # directive, because otherwise it's too hard to figure out where the `import` directive ends. + import-directive: + beginPattern: '#import' + # Ends with a simple-id that is not followed by a `.` or a `::`. This does not handle comments or + # line breaks between the simple-id and the `.` or `::`. + end: '(?#simple-id) (?!\s*(\.|\:\:))' + endCaptures: + '0': + name: entity.name.type.namespace.ql + name: meta.block.import-directive.ql + patterns: + - include: '#non-context-sensitive' + - match: '(?#simple-id)' + name: entity.name.type.namespace.ql + + # The end pattern for an `as` clause, whether on an `import` directive, in an aggregate, or on a + # `select`. + end-of-as-clause: + match: '(?: (?<=(?#id-character)) (?!(?#id-character)) (? + + + diff --git a/extensions/ql-vscode/test/pure-tests/location.test.ts b/extensions/ql-vscode/test/pure-tests/location.test.ts new file mode 100644 index 000000000..b45881365 --- /dev/null +++ b/extensions/ql-vscode/test/pure-tests/location.test.ts @@ -0,0 +1,32 @@ +import { expect } from 'chai'; +import 'mocha'; +import { LocationStyle, StringLocation, tryGetWholeFileLocation } from 'semmle-bqrs'; + +describe('processing string locations', function () { + + it('should detect Windows whole-file locations', function () { + const loc: StringLocation = { + t: LocationStyle.String, + loc: 'file://C:/path/to/file.ext:0:0:0:0' + }; + const wholeFileLoc = tryGetWholeFileLocation(loc); + expect(wholeFileLoc).to.eql({t: LocationStyle.WholeFile, file: 'C:/path/to/file.ext'}); + }); + it('should detect Unix whole-file locations', function () { + const loc: StringLocation = { + t: LocationStyle.String, + loc: 'file:///path/to/file.ext:0:0:0:0' + }; + const wholeFileLoc = tryGetWholeFileLocation(loc); + expect(wholeFileLoc).to.eql({t: LocationStyle.WholeFile, file: '/path/to/file.ext'}); + }); + it('should ignore other string locations', function () { + for (const loc of ['file:///path/to/file.ext', 'I am not a location']) { + const wholeFileLoc = tryGetWholeFileLocation({ + t: LocationStyle.String, + loc: loc + }); + expect(wholeFileLoc).to.be.undefined; + } + }); +}); diff --git a/extensions/ql-vscode/test/pure-tests/query-test.ts b/extensions/ql-vscode/test/pure-tests/query-test.ts new file mode 100644 index 000000000..66b68c9cc --- /dev/null +++ b/extensions/ql-vscode/test/pure-tests/query-test.ts @@ -0,0 +1,227 @@ +import { expect } from 'chai'; +import * as fs from 'fs-extra'; +import 'mocha'; +import * as path from 'path'; +import * as bqrs from 'semmle-bqrs'; +import { FileReader } from 'semmle-io-node'; +import * as tmp from 'tmp'; +import * as url from 'url'; +import { CancellationTokenSource } from 'vscode-jsonrpc'; +import * as messages from '../../src/messages'; +import * as qsClient from '../../src/queryserver-client'; +import * as cli from '../../src/cli'; +import { ProgressReporter } from '../../src/logging'; + + +declare module "url" { + export function pathToFileURL(urlStr: string): Url; +} + +const tmpDir = tmp.dirSync({ prefix: 'query_test_', keep: false, unsafeCleanup: true }); + +const COMPILED_QUERY_PATH = path.join(tmpDir.name, 'compiled.qlo'); +const RESULTS_PATH = path.join(tmpDir.name, 'results.bqrs'); + +const source = new CancellationTokenSource(); +const token = source.token; + +class Checkpoint { + private res: () => void; + private rej: (e: Error) => void; + private promise: Promise; + + constructor() { + this.res = () => { }; + this.rej = () => { }; + this.promise = new Promise((res, rej) => { this.res = res; this.rej = rej; }) + } + + async done() { + return this.promise; + } + + async resolve() { + (this.res)(); + } + + async reject(e: Error) { + (this.rej)(e); + } +} + +type ResultSets = { + [name: string]: bqrs.ColumnValue[][] +} + +type QueryTestCase = { + queryPath: string, + expectedResultSets: ResultSets +} + +// Test cases: queries to run and their expected results. +const queryTestCases: QueryTestCase[] = [ + { + queryPath: path.join(__dirname, '../data/query.ql'), + expectedResultSets: { + '#select': [[42, 3.14159, "hello world", true]] + } + }, + { + queryPath: path.join(__dirname, '../data/multiple-result-sets.ql'), + expectedResultSets: { + 'edges': [[1, 2], [2, 3]], + '#select': [["s"]] + } + } +]; + +describe('using the query server', function () { + before(function () { + if (process.env["CODEQL_PATH"] === undefined) { + console.log('The environment variable CODEQL_PATH is not set. The query server tests, which require the CodeQL CLI, will be skipped.'); + this.skip(); + } + }); + + const codeQlPath = process.env["CODEQL_PATH"]!; + let qs: qsClient.QueryServerClient; + let cliServer: cli.CodeQLCliServer; + after(() => { + if (qs) { + qs.dispose(); + } + if (cliServer) { + cliServer.dispose(); + } + }); + it('should be able to start the query server', async function () { + const consoleProgressReporter: ProgressReporter = { + report: (v: {message: string}) => console.log(`progress reporter says ${v.message}`) + }; + const logger = { + log: (s: string) => console.log('logger says', s), + logWithoutTrailingNewline: (s: string) => { } + }; + cliServer = new cli.CodeQLCliServer({ + async getCodeQlPathWithoutVersionCheck(): Promise { + return codeQlPath; + }, + }, logger) + qs = new qsClient.QueryServerClient( + { + codeQlPath, + numThreads: 1, + queryMemoryMb: 1024, + timeoutSecs: 1000, + debug: false + }, + cliServer, + { + logger + }, + task => task(consoleProgressReporter, token) + ); + await qs.startQueryServer(); + }); + + // Note this does not work with arrow functions as the test case bodies: + // ensure they are all written with standard anonymous functions. + this.timeout(5000); + + for (const queryTestCase of queryTestCases) { + const queryName = path.basename(queryTestCase.queryPath); + const compilationSucceeded = new Checkpoint(); + const evaluationSucceeded = new Checkpoint(); + + it(`should be able to compile query ${queryName}`, async function () { + expect(fs.existsSync(queryTestCase.queryPath)).to.be.true; + try { + const qlProgram: messages.QlProgram = { + libraryPath: [], + dbschemePath: path.join(__dirname, '../data/test.dbscheme'), + queryPath: queryTestCase.queryPath + }; + const params: messages.CompileQueryParams = { + compilationOptions: { + computeNoLocationUrls: true, + failOnWarnings: false, + fastCompilation: false, + includeDilInQlo: true, + localChecking: false, + noComputeGetUrl: false, + noComputeToString: false, + }, + queryToCheck: qlProgram, + resultPath: COMPILED_QUERY_PATH, + target: { query: {} } + }; + const result = await qs.sendRequest(messages.compileQuery, params, token, () => { }); + expect(result.messages!.length).to.equal(0); + compilationSucceeded.resolve(); + } + catch (e) { + compilationSucceeded.reject(e); + } + }); + + it(`should be able to run query ${queryName}`, async function () { + try { + await compilationSucceeded.done(); + const callbackId = qs.registerCallback(res => { + evaluationSucceeded.resolve(); + }); + const queryToRun: messages.QueryToRun = { + resultsPath: RESULTS_PATH, + qlo: url.pathToFileURL(COMPILED_QUERY_PATH).toString(), + allowUnknownTemplates: true, + id: callbackId, + timeoutSecs: 1000, + }; + const db: messages.Dataset = { + dbDir: path.join(__dirname, '../test-db'), + workingSet: 'default', + } + const params: messages.EvaluateQueriesParams = { + db, + evaluateId: callbackId, + queries: [queryToRun], + stopOnError: false, + useSequenceHint: false + }; + await qs.sendRequest(messages.runQueries, params, token, () => { }); + } + catch (e) { + evaluationSucceeded.reject(e); + } + }); + + const actualResultSets: ResultSets = {}; + it(`should be able to parse results of query ${queryName}`, async function () { + let fileReader: FileReader | undefined; + try { + await evaluationSucceeded.done(); + fileReader = await FileReader.open(RESULTS_PATH); + const resultSetsReader = await bqrs.open(fileReader); + for (const reader of resultSetsReader.resultSets) { + const actualRows: bqrs.ColumnValue[][] = []; + for await (const row of reader.readTuples()) { + actualRows.push(row); + } + actualResultSets[reader.schema.name] = actualRows; + } + } finally { + if (fileReader) { + fileReader.dispose(); + } + } + }); + + it(`should have correct results for query ${queryName}`, async function () { + expect(actualResultSets!).not.to.be.empty; + expect(Object.keys(actualResultSets!).sort()).to.eql(Object.keys(queryTestCase.expectedResultSets).sort()); + for (const name in queryTestCase.expectedResultSets) { + expect(actualResultSets![name]).to.eql(queryTestCase.expectedResultSets[name], `Results for query predicate ${name} do not match`); + } + }); + } +}); diff --git a/extensions/ql-vscode/tsconfig.json b/extensions/ql-vscode/tsconfig.json new file mode 100644 index 000000000..aa011be95 --- /dev/null +++ b/extensions/ql-vscode/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./node_modules/typescript-config/extension.tsconfig.json" +} \ No newline at end of file diff --git a/lib/semmle-bqrs/gulpfile.js/index.js b/lib/semmle-bqrs/gulpfile.js/index.js new file mode 100644 index 000000000..8d874e2d2 --- /dev/null +++ b/lib/semmle-bqrs/gulpfile.js/index.js @@ -0,0 +1,7 @@ +'use strict'; + +require('ts-node').register({}); +const { compileTypeScript, watchTypeScript } = require('build-tasks'); + +exports.default = compileTypeScript; +exports.watchTypeScript = watchTypeScript; diff --git a/lib/semmle-bqrs/package.json b/lib/semmle-bqrs/package.json new file mode 100644 index 000000000..d78c5db12 --- /dev/null +++ b/lib/semmle-bqrs/package.json @@ -0,0 +1,32 @@ +{ + "name": "semmle-bqrs", + "description": "Parses Binary Query Result Sets generated by CodeQL", + "author": "GitHub", + "private": true, + "version": "0.0.1", + "publisher": "GitHub", + "repository": { + "type": "git", + "url": "https://github.com/github/vscode-codeql" + }, + "main": "./out/index", + "files": [ + "out/**", + "package.json" + ], + "scripts": { + "build": "gulp", + "format": "tsfmt -r" + }, + "dependencies": { + "leb": "^0.3.0", + "reflect-metadata": "~0.1.13", + "semmle-io": "^0.0.1" + }, + "devDependencies": { + "@types/node": "^12.0.8", + "build-tasks": "^0.0.1", + "typescript-config": "^0.0.1", + "typescript-formatter": "^7.2.2" + } +} \ No newline at end of file diff --git a/lib/semmle-bqrs/src/bqrs-custom.ts b/lib/semmle-bqrs/src/bqrs-custom.ts new file mode 100644 index 000000000..b3b780330 --- /dev/null +++ b/lib/semmle-bqrs/src/bqrs-custom.ts @@ -0,0 +1,407 @@ +import { ResultSetSchema, LocationStyle, ColumnTypeKind } from "./bqrs-schema"; +import { ResultSetsReader, ResultSetReader } from "./bqrs-file"; +import { ElementBase, ColumnValue } from "./bqrs-results"; + +/** + * Represents a binding to all remaining columns, starting at the column index specified by + * `startColumn`. + */ +export interface RestColumnIndex { + startColumn: number +} + +/** + * Indentifies the result column to which a property is bound. May be the index of a specific + * column, or an instance of `RestColumnIndex` to bind to all remaining columns. + */ +export type ColumnIndex = number | RestColumnIndex; + +/** + * Options that can be specified for a `@qlTable` attribute. + */ +export interface TableOptions { + /** + * The name of the table to bind to. If multiple values are specified, the property is bound to + * the the table whose name is earliest in the list. + */ + name?: string | string[]; +} + +export enum QLOption { + Required = 'required', + Optional = 'optional', + Forbidden = 'forbidden' +} + +/** + * Options that can be specified for a `@qlElement` attribute. + */ +export interface ElementOptions { + label?: QLOption; + location?: QLOption; +} + +/** + * An attribute that binds the target property to a result column representing a QL element. + * @param index Index of the column to be bound. + * @param options Binding options. + */ +export function qlElement(index: ColumnIndex, options: ElementOptions = {}): PropertyDecorator { + return (proto: any, key: PropertyKey): void => { + column(proto, { + key: key, + index: index, + type: 'e', + options: { + label: options.label ? options.label : QLOption.Required, + location: options.location ? options.location : QLOption.Required + } + }); + } +} + +/** + * An attribute that binds the target property to a result column containing a QL string. + * @param index Index of the column to be bound. + */ +export function qlString(index: ColumnIndex): PropertyDecorator { + return (proto: any, key: PropertyKey): void => { + column(proto, { + key: key, + index: index, + type: 's' + }); + } +} + +/** + * An attribute that binds the target property to a set of result columns. The individual + * columns are bound to properties of the underlying type of the target property. + * @param index Index of the first column to be bound. + * @param type The type of the property. + */ +export function qlTuple(index: ColumnIndex, type: { new(): any }): PropertyDecorator { + return (proto: any, key: PropertyKey): void => { + column(proto, { + key: key, + index: index, + type: type + }); + } +} + +type PropertyKey = string | symbol; + +interface ColumnProperty { + key: PropertyKey; + index: ColumnIndex; + type: ColumnTypeKind | { new(): any }; +} + +interface ElementProperty extends ColumnProperty { + type: 'e'; + options: Required; +} + +function isElement(property: ColumnProperty): property is ElementProperty { + return property.type === 'e'; +} + +const columnPropertiesSymbol = Symbol('columnProperties'); + +type PropertyDecorator = (proto: any, key: PropertyKey) => void; + +function column(proto: any, property: T): void { + let columnProperties: ColumnProperty[] | undefined = Reflect.getMetadata(columnPropertiesSymbol, proto); + if (columnProperties === undefined) { + columnProperties = []; + Reflect.defineMetadata(columnPropertiesSymbol, columnProperties, proto); + } + columnProperties.push(property); +} + +interface TableProperty { + key: PropertyKey; + tableNames: string[]; + rowType: any; +} + +const tablePropertiesSymbol = Symbol('tableProperties'); + +/** + * An attribute that binds the target property to the contents of a result table. + * @param rowType The type representing a single row in the bound table. The type of the target + * property must be an array of this type. + * @param options Binding options. + */ +export function qlTable(rowType: any, options?: TableOptions): any { + return (proto, key: PropertyKey) => { + const realOptions = options || {}; + let names: string[]; + if (realOptions.name === undefined) { + names = [key.toString()] + } + else if (typeof realOptions.name === 'string') { + names = [realOptions.name]; + } + else { + names = realOptions.name; + } + + let tableProperties: TableProperty[] | undefined = Reflect.getMetadata(tablePropertiesSymbol, proto); + if (tableProperties === undefined) { + tableProperties = []; + Reflect.defineMetadata(tablePropertiesSymbol, tableProperties, proto); + } + tableProperties.push({ + key: key, + tableNames: names, + rowType: rowType + }); + }; +} + +type ParseTupleAction = (src: readonly ColumnValue[], dest: any) => void; + +type TupleParser = (src: readonly ColumnValue[]) => T; + +export class CustomResultSet { + public constructor(private reader: ResultSetReader, private readonly type: { new(): TTuple }, + private readonly tupleParser: TupleParser) { + } + + public async* readTuples(): AsyncIterableIterator { + for await (const tuple of this.reader.readTuples()) { + yield this.tupleParser(tuple); + } + } +} + +class CustomResultSetBinder { + private readonly boundColumns: boolean[]; + + private constructor(private readonly rowType: { new(): any }, + private readonly schema: ResultSetSchema) { + + this.boundColumns = Array(schema.columns.length).fill(false); + } + + public static bind(reader: ResultSetReader, rowType: { new(): TTuple }): + CustomResultSet { + + const binder = new CustomResultSetBinder(rowType, reader.schema); + const tupleParser = binder.bindRoot(); + + return new CustomResultSet(reader, rowType, tupleParser); + } + + private bindRoot(): TupleParser { + const { action } = this.bindObject(this.rowType, 0, true); + const unboundColumnIndex = this.boundColumns.indexOf(false); + if (unboundColumnIndex >= 0) { + throw new Error(`Column '${this.schema.name}[${unboundColumnIndex}]' is not bound to a property.`); + } + + return tuple => { + let result = new this.rowType; + action(tuple, result); + return result; + } + } + + private checkElementProperty(index: ColumnIndex, propertyName: 'location' | 'label', + hasProperty: boolean, expectsProperty: QLOption): void { + + switch (expectsProperty) { + case QLOption.Required: + if (!hasProperty) { + throw new Error(`Element column '${this.schema.name}[${index}]' does not have the required '${propertyName}' property.`); + } + break; + + case QLOption.Forbidden: + if (!hasProperty) { + throw new Error(`Element column '${this.schema.name}[${index}]' has unexpected '${propertyName}' property.`); + } + break; + + case QLOption.Optional: + break; + } + } + + private bindObject(type: { new(): any }, startIndex: number, isRoot: boolean): { + action: ParseTupleAction, + lastColumn: number + } { + + const columnProperties: ColumnProperty[] | undefined = + Reflect.getMetadata(columnPropertiesSymbol, type.prototype); + if (columnProperties === undefined) { + throw new Error(`Type '${type.toString()}' does not have any properties decorated with '@column'.`); + } + + const actions: ParseTupleAction[] = []; + let restProperty: ColumnProperty | undefined = undefined; + + let lastColumn = startIndex; + for (const property of columnProperties) { + if (typeof property.index === 'object') { + if (!isRoot) { + throw new Error(`Type '${type.toString()}' has a property bound to '...', but is not the root type.`); + } + if (restProperty !== undefined) { + throw new Error(`Type '${type.toString()}' has multiple properties bound to '...'.`); + } + restProperty = property; + } + else { + const index = property.index + startIndex; + const { action, lastColumn: lastChildColumn } = this.bindColumn(index, type, property, + property.key); + actions.push(action); + lastColumn = Math.max(lastColumn, lastChildColumn); + } + } + + if (restProperty !== undefined) { + const startIndex = (restProperty.index).startColumn; + let index = startIndex; + let elementIndex = 0; + const elementActions: ParseTupleAction[] = []; + while (index < this.schema.columns.length) { + const { action, lastColumn: lastChildColumn } = this.bindColumn(index, type, restProperty, elementIndex); + elementActions.push(action); + index = lastChildColumn + 1; + elementIndex++; + } + + const key = restProperty.key; + actions.push((src, dest) => { + const destArray = Array(elementActions.length); + elementActions.forEach(action => action(src, destArray)); + dest[key] = destArray; + }); + } + + return { + action: (src, dest) => actions.forEach(action => action(src, dest)), + lastColumn: lastColumn + }; + } + + private bindColumn(index: number, type: new () => any, property: ColumnProperty, + key: PropertyKey | number): { + action: ParseTupleAction, + lastColumn: number + } { + + if ((index < 0) || (index >= this.schema.columns.length)) { + throw new Error(`No matching column '${index}' found for property '${type.toString()}.${property.key.toString()}' when binding root type '${this.rowType.toString()}'.`); + } + if (typeof property.type === 'string') { + // This property is bound to a single column + return { + action: this.bindSingleColumn(index, property, type, key), + lastColumn: index + }; + } + else { + // This property is a tuple that has properties that are bound to columns. + const propertyType = property.type; + const { action: objectParser, lastColumn: lastChildColumn } = this.bindObject(propertyType, index, false); + return { + action: (src, dest) => { + const destObject = new propertyType; + objectParser(src, destObject); + dest[key] = destObject; + }, + lastColumn: lastChildColumn + }; + } + } + + private bindSingleColumn(index: number, property: ColumnProperty, type: new () => any, + key: PropertyKey | number): ParseTupleAction { + + if (this.boundColumns[index]) { + throw new Error(`Column '${this.schema.name}[${index}]' is bound to multiple columns in root type '${this.rowType.toString()}'.`); + } + const column = this.schema.columns[index]; + if (column.type.type !== property.type) { + throw new Error(`Column '${this.schema.name}[${index}]' has type '${column.type.type}', but property '${type.toString()}.${property.key.toString()}' expected type '${property.type}'.`); + } + this.boundColumns[index] = true; + + if (isElement(property) && (column.type.type === 'e')) { + const hasLabel = column.type.hasLabel; + this.checkElementProperty(index, 'label', hasLabel, property.options.label); + const hasLocation = column.type.locationStyle !== LocationStyle.None; + this.checkElementProperty(index, 'location', hasLocation, property.options.location); + return (src, dest) => { + const srcElement = src[index]; + const destElement: ElementBase = { + id: srcElement.id + }; + if (hasLabel) { + destElement.label = srcElement.label; + } + if (hasLocation) { + destElement.location = srcElement.location; + } + dest[key] = destElement; + }; + } + else { + return (src, dest) => { + dest[key] = src[index]; + }; + } + } +} + +type ArrayElementType = T extends Array ? U : never; + +export type CustomResultSets = { + [P in keyof T]: CustomResultSet>; +} + +export function createCustomResultSets(reader: ResultSetsReader, type: { new(): T }): + CustomResultSets { + + const tableProperties: TableProperty[] | undefined = Reflect.getMetadata(tablePropertiesSymbol, type.prototype); + if (tableProperties === undefined) { + throw new Error(`Type '${type.toString()}' does not have any properties decorated with '@table'.`); + } + + const customResultSets: Partial> = {}; + + const boundProperties = new Set(); + + for (const resultSet of reader.resultSets) { + const tableProperty = findPropertyForTable(resultSet.schema, tableProperties); + if (tableProperty === undefined) { + throw new Error(`No matching property found for result set '${resultSet.schema.name}'.`); + } + if (boundProperties.has(tableProperty.key)) { + throw new Error(`Multiple result sets bound to property '${tableProperty.key.toString()}'.`); + } + boundProperties.add(tableProperty.key); + customResultSets[tableProperty.key] = CustomResultSetBinder.bind(resultSet, + tableProperty.rowType); + } + for (const tableProperty of tableProperties) { + if (!boundProperties.has(tableProperty.key)) { + throw new Error(`No matching table found for property '${tableProperty.key.toString()}'.`); + } + } + + return >customResultSets; +} + +function findPropertyForTable(resultSet: ResultSetSchema, tableProperties: TableProperty[]): + TableProperty | undefined { + + const tableName = resultSet.name === '#select' ? 'select' : resultSet.name; + return tableProperties.find(tableProperty => tableProperty.tableNames.find(name => name === tableName)); +} diff --git a/lib/semmle-bqrs/src/bqrs-file.ts b/lib/semmle-bqrs/src/bqrs-file.ts new file mode 100644 index 000000000..34a9916e8 --- /dev/null +++ b/lib/semmle-bqrs/src/bqrs-file.ts @@ -0,0 +1,191 @@ +import { RandomAccessReader, StreamDigester } from 'semmle-io'; +import { parseResultSetsHeader, StringPool, parseResultSetSchema, readTuples } from './bqrs-parse'; +import { ResultSetsSchema, ResultSetSchema } from './bqrs-schema'; +import { ColumnValue } from './bqrs-results'; + +/** + * The result of parsing data from a specific file region. + */ +interface RegionResult { + /** The parsed data. */ + result: T, + /** The exclusive end position of the parsed data in the file. */ + finalOffset: number +} + +/** Reads data from the specified region of the file, and parses it using the given function. */ +async function inFileRegion( + file: RandomAccessReader, + start: number, + end: number | undefined, + parse: (d: StreamDigester) => Promise +): Promise> { + const stream = file.readStream(start, end); + try { + const d = StreamDigester.fromChunkIterator(stream); + const result = await parse(d); + + return { + result: result, + finalOffset: start + d.position + }; + } + finally { + stream.dispose(); + } +} + +/** + * A single result set in a BQRS file. + */ +export interface ResultSetReader { + /** + * The schema that describes the result set. + */ + readonly schema: ResultSetSchema; + /** + * Reads all of the tuples in the result set. + */ + readTuples(): AsyncIterableIterator; +} + +/** + * A Binary Query Result Sets ("BQRS") file. + * + * @remarks + * Allows independant access to individual tables without having to parse the entire file up front. + */ +export interface ResultSetsReader { + readonly schema: ResultSetsSchema; + readonly resultSets: readonly ResultSetReader[]; + + findResultSetByName(name: string): ResultSetReader | undefined; +} + +/** + * Metadata for a single `ResultSet` in a BQRS file. + * Does not contain the result tuples themselves. + * Includes the offset and length of the tuple data in the file, + * which can be used to read the tuples. + */ +interface ResultSetInfo { + schema: ResultSetSchema; + rowsOffset: number; + rowsLength: number; +} + +class ResultSetReaderImpl implements ResultSetReader { + public readonly schema: ResultSetSchema; + private readonly rowsOffset: number; + private readonly rowsLength: number; + + public constructor(private readonly resultSets: ResultSetsReaderImpl, info: ResultSetInfo) { + this.schema = info.schema; + this.rowsOffset = info.rowsOffset; + this.rowsLength = info.rowsLength; + } + + public async* readTuples(): AsyncIterableIterator { + const stream = this.resultSets.file.readStream(this.rowsOffset, + this.rowsOffset + this.rowsLength); + try { + const d = StreamDigester.fromChunkIterator(stream); + for await (const tuple of readTuples(d, this.schema, await this.resultSets.getStringPool())) { + yield tuple; + } + } + finally { + stream.dispose(); + } + } +} + +class ResultSetsReaderImpl implements ResultSetsReader { + private stringPool?: StringPool = undefined; + private readonly _resultSets: ResultSetReaderImpl[]; + + private constructor(public readonly file: RandomAccessReader, + public readonly schema: ResultSetsSchema, resultSets: ResultSetInfo[], + private readonly stringPoolOffset: number) { + + this._resultSets = resultSets.map((info) => { + return new ResultSetReaderImpl(this, info); + }); + } + + public get resultSets(): readonly ResultSetReader[] { + return this._resultSets; + } + + public findResultSetByName(name: string): ResultSetReader | undefined { + return this._resultSets.find((resultSet) => resultSet.schema.name === name); + } + + public async getStringPool(): Promise { + if (this.stringPool === undefined) { + const { result: stringPoolBuffer } = await inFileRegion(this.file, this.stringPoolOffset, + this.stringPoolOffset + this.schema.stringPoolSize, + async d => await d.read(this.schema.stringPoolSize)); + this.stringPool = new StringPool(stringPoolBuffer); + } + + return this.stringPool; + } + + public static async open(file: RandomAccessReader): Promise { + // Parse the header of the entire BQRS file. + const { result: header, finalOffset: stringPoolOffset } = + await inFileRegion(file, 0, undefined, d => parseResultSetsHeader(d)); + + // The header is followed by a shared string pool. + // We have saved the offset and length of the string pool within the file, + // so we can read it later when needed. + // For now, skip over the string pool to reach the starting point of the first result set. + let currentResultSetOffset = stringPoolOffset + header.stringPoolSize; + + // Parse information about each result set within the file. + const resultSets: ResultSetInfo[] = []; + for (let resultSetIndex = 0; resultSetIndex < header.resultSetCount; resultSetIndex++) { + // Read the length of this result set (encoded as a single byte). + // Note: reading length and schema together from a file region may be more efficient. + // Reading them separately just makes it easier to compute the + // starting offset and length of the schema. + const { result: resultSetLength, finalOffset: resultSetSchemaOffset } = + await inFileRegion(file, currentResultSetOffset, undefined, d => d.readLEB128UInt32()); + + // Read the schema of this result set. + const { result: resultSetSchema, finalOffset: resultSetRowsOffset } = + await inFileRegion(file, resultSetSchemaOffset, undefined, d => parseResultSetSchema(d)); + const resultSetSchemaLength = resultSetRowsOffset - resultSetSchemaOffset; + + // The schema is followed by the tuple/row data for the result set. + // We save the offset and length of the tuple data within the file, + // so we can read it later when needed. + const info: ResultSetInfo = { + // length of result set = length of schema + length of tuple data + // The 1 byte that encodes the length itself is not counted. + rowsLength: resultSetLength - resultSetSchemaLength, + rowsOffset: resultSetRowsOffset, + schema: resultSetSchema, + }; + resultSets.push(info); + // Skip over the tuple data of the current result set, + // to reach the starting offset of the next result set. + currentResultSetOffset = info.rowsOffset + info.rowsLength; + } + + const schema: ResultSetsSchema = { + version: header.version, + stringPoolSize: header.stringPoolSize, + resultSets: resultSets.map(resultSet => resultSet.schema) + }; + + const reader = new ResultSetsReaderImpl(file, schema, resultSets, stringPoolOffset); + + return reader; + } +} + +export function open(file: RandomAccessReader): Promise { + return ResultSetsReaderImpl.open(file); +} diff --git a/lib/semmle-bqrs/src/bqrs-parse.ts b/lib/semmle-bqrs/src/bqrs-parse.ts new file mode 100644 index 000000000..d1f775407 --- /dev/null +++ b/lib/semmle-bqrs/src/bqrs-parse.ts @@ -0,0 +1,202 @@ +import { decodeUInt32 } from 'leb'; +import { StreamDigester } from 'semmle-io'; +import { ColumnValue, RawLocationValue } from './bqrs-results'; +import { ColumnSchema, ColumnType, LocationStyle, PrimitiveTypeKind, ResultSetSchema } from './bqrs-schema'; + +/** + * bqrs-parse.ts + * ------- + * + * Parsing Binary Query Result Set files. + * See [[https://git.semmle.com/Semmle/code/tree/master/queryserver-client/src/com/semmle/api/result/BinaryQueryResultSets.java]]. + */ + +const RESULT_SET_VERSION = 1; +const RESULT_SETS_VERSION = 2; + +export type TupleParser = (tuple: readonly ColumnValue[]) => void; + +export interface ResultSetsHeader { + version: number, + resultSetCount: number, + stringPoolSize: number +} + +async function parseResultColumnType(d: StreamDigester): Promise { + const t = await d.readASCIIChar(); + if (t === 'e') { + const primitiveType: PrimitiveTypeKind = + (await d.readASCIIChar()) as PrimitiveTypeKind; + const hasLabel = (await d.readByte()) !== 0; + const locationStyle = await d.readByte(); + return { type: 'e', locationStyle, hasLabel, primitiveType }; + } + else { + return { type: t }; + } +} + +async function parseColumnSchema(d: StreamDigester): Promise { + const numColumns = await d.readLEB128UInt32(); + const rv: ColumnSchema[] = []; + for (let i = 0; i < numColumns; i++) { + const name = await readLengthPrefixedString(d); + const type = await parseResultColumnType(d); + rv.push({ name, type }); + } + return rv; +} + +function getTrueStringLength(encodedLength: number): number { + const stringLength = (encodedLength as number) - 1; + if (stringLength === -1) { + // XXX why is this a possibility? Does a '(-1)-length' string + // (i.e. a single 0x00 byte) mean something different from a + // 0-length string? (i.e. a single 0x01 byte) + return 0; + } + else { + return stringLength; + } +} + +export class StringPool { + public constructor(private readonly buffer: Buffer) { + } + + public getString(offset: number): string { + //TODO: Memoize? + const { value: encodedStringLength, nextIndex } = decodeUInt32(this.buffer, offset); + const stringLength = getTrueStringLength(encodedStringLength); + + const value = this.buffer.toString('utf8', nextIndex, nextIndex + stringLength); + return value; + } +} + +export async function parseResultSetsHeader(d: StreamDigester): Promise { + const version = await d.readLEB128UInt32(); + if (version !== RESULT_SETS_VERSION) { + throw new Error(`Mismatched binary query results version. Got '${version}', but expected '${RESULT_SETS_VERSION}'.`); + } + const resultSetCount = await d.readLEB128UInt32(); + const stringPoolSize = await d.readLEB128UInt32(); + + return { + version: version, + stringPoolSize: stringPoolSize, + resultSetCount: resultSetCount + }; +} + +async function readLengthPrefixedString(d: StreamDigester): Promise { + const encodedLength = await d.readLEB128UInt32(); + const stringLength = getTrueStringLength(encodedLength); + return await d.readUTF8String(stringLength); +} + +export async function parseResultSetSchema(d: StreamDigester): Promise { + const version = await d.readLEB128UInt32(); + if (version !== RESULT_SET_VERSION) { + throw new Error(`Mismatched binary query result version. Got '${version}', but expected '${RESULT_SET_VERSION}'.`); + } + const name = await readLengthPrefixedString(d); + const tupleCount = await d.readLEB128UInt32(); + const columns = await parseColumnSchema(d); + + return { + version: version, + name: name, + tupleCount: tupleCount, + columns: columns + }; +} + +async function parseString(d: StreamDigester, pool: StringPool): Promise { + const stringOffset = await d.readLEB128UInt32(); + const value = pool.getString(stringOffset); + return value; +} + +async function parseLocation(d: StreamDigester, t: LocationStyle, pool: StringPool): + Promise { + + switch (t) { + case LocationStyle.None: return undefined; + case LocationStyle.String: return { t, loc: await parseString(d, pool) }; + case LocationStyle.FivePart: { + const file = await parseString(d, pool); + const lineStart = await d.readLEB128UInt32(); + const colStart = await d.readLEB128UInt32(); + const lineEnd = await d.readLEB128UInt32(); + const colEnd = await d.readLEB128UInt32(); + return { t, file, lineStart, colStart, lineEnd, colEnd }; + } + case LocationStyle.WholeFile: + throw new Error('Whole-file locations should appear as string locations in BQRS files.'); + } + throw new Error(`Unknown Location Style ${t}`); +} + +async function parsePrimitiveColumn(d: StreamDigester, type: PrimitiveTypeKind, + pool: StringPool): Promise { + + switch (type) { + case 's': return await parseString(d, pool); + case 'b': return await d.readByte() !== 0; + case 'i': return await d.readLEB128UInt32(); + case 'f': return await d.readDoubleLE(); + case 'd': return await d.readDate(); + case 'u': return await parseString(d, pool); + default: throw new Error(`Unknown primitive column type '${type}'.`); + } +} + +export async function parseColumn(d: StreamDigester, t: ColumnType, pool: StringPool): + Promise { + + if (t.type === 'e') { + let primitive = await parsePrimitiveColumn(d, t.primitiveType, pool); + const label = t.hasLabel ? await parseString(d, pool) : undefined; + const loc = await parseLocation(d, t.locationStyle, pool); + return { + id: primitive, + label: label, + location: loc + }; + } + else { + return parsePrimitiveColumn(d, t.type, pool); + } +} + +export async function* readTuples(d: StreamDigester, schema: ResultSetSchema, + stringPool: StringPool): AsyncIterableIterator { + + const { tupleCount, columns } = schema; + + for (let rowIndex = 0; rowIndex < tupleCount; rowIndex++) { + const tuple: ColumnValue[] = Array(columns.length); + for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) { + tuple[columnIndex] = await parseColumn(d, columns[columnIndex].type, stringPool); + } + yield tuple; + } +} + +export async function parseTuples(d: StreamDigester, schema: ResultSetSchema, + stringPool: StringPool, tupleParser: TupleParser): Promise { + + const { tupleCount, columns } = schema; + + // Create a single temporary tuple to hold the values we read from each row. Fill it with + // zero values initially so that we don't have to type it as `TupleValue | undefined`. + const tempTuple: ColumnValue[] = Array(columns.length).fill(0); + + for (let rowIndex = 0; rowIndex < tupleCount; rowIndex++) { + for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) { + tempTuple[columnIndex] = await parseColumn(d, columns[columnIndex].type, stringPool); + } + tupleParser(tempTuple); + } +} diff --git a/lib/semmle-bqrs/src/bqrs-results.ts b/lib/semmle-bqrs/src/bqrs-results.ts new file mode 100644 index 000000000..df3dad794 --- /dev/null +++ b/lib/semmle-bqrs/src/bqrs-results.ts @@ -0,0 +1,100 @@ +import { LocationStyle } from './bqrs-schema'; + +// See https://help.semmle.com/QL/learn-ql/ql/locations.html for how these are used. +export interface FivePartLocation { + t: LocationStyle.FivePart; + file: string; + lineStart: number; + colStart: number; + lineEnd: number; + colEnd: number; +} + +export interface StringLocation { + t: LocationStyle.String; + loc: string; +} + +/** + * A location representing an entire filesystem resource. + * This is usually derived from a `StringLocation` with the entire filesystem URL. + */ +export interface WholeFileLocation { + t: LocationStyle.WholeFile; + file: string; +} + +export type RawLocationValue = FivePartLocation | StringLocation; + +export type LocationValue = RawLocationValue | WholeFileLocation; + +/** A location that may be resolved to a source code element. */ +export type ResolvableLocationValue = FivePartLocation | WholeFileLocation; + + +/** + * The CodeQL filesystem libraries use this pattern in `getURL()` predicates + * to describe the location of an entire filesystem resource. + * Such locations appear as `StringLocation`s instead of `FivePartLocation`s. + * + * Folder resources also get similar URLs, but with the `folder` scheme. + * They are deliberately ignored here, since there is no suitable location to show the user. + */ +const WHOLE_FILE_LOCATION_REGEX = /file:\/\/(.+):0:0:0:0/; + +/** + * Gets a resolvable source file location for the specified `LocationValue`, if possible. + * @param loc The location to test. + */ +export function tryGetResolvableLocation(loc: LocationValue | undefined): ResolvableLocationValue | undefined { + if (loc === undefined) { + return undefined; + } + else if ((loc.t === LocationStyle.FivePart) && loc.file) { + return loc; + } + else if ((loc.t === LocationStyle.WholeFile) && loc.file) { + return loc; + } + else if ((loc.t === LocationStyle.String) && loc.loc) { + return tryGetWholeFileLocation(loc); + } + else { + return undefined; + } +} + +export function tryGetWholeFileLocation(loc: StringLocation): WholeFileLocation | undefined { + const matches = WHOLE_FILE_LOCATION_REGEX.exec(loc.loc); + if (matches && matches.length > 1 && matches[1]) { + // Whole-file location. + // We could represent this as a FivePartLocation with all numeric fields set to zero, + // but that would be a deliberate misuse as those fields are intended to be 1-based. + return { + t: LocationStyle.WholeFile, + file: matches[1] + }; + } else { + return undefined; + } +} + +export interface ElementBase { + id: PrimitiveColumnValue; + label?: string; + location?: LocationValue; +} + +export interface ElementWithLabel extends ElementBase { + label: string; +} + +export interface ElementWithLocation extends ElementBase { + location: LocationValue; +} + +export interface Element extends Required { +} + +export type PrimitiveColumnValue = string | boolean | number | Date; +export type ColumnValue = PrimitiveColumnValue | ElementBase; diff --git a/lib/semmle-bqrs/src/bqrs-schema.ts b/lib/semmle-bqrs/src/bqrs-schema.ts new file mode 100644 index 000000000..8880a27fb --- /dev/null +++ b/lib/semmle-bqrs/src/bqrs-schema.ts @@ -0,0 +1,66 @@ +export enum LocationStyle { + None = 0, + String, + FivePart, + /** Does not occur in BQRS files. Used only to distinguish whole-file locations in client code. */ + WholeFile +} + +/** + * A primitive type (any type other than an element). + */ +export type PrimitiveTypeKind = 's' | 'b' | 'i' | 'f' | 'd' | 'u'; + +/** + * A kind of type that a column may have. + */ +export type ColumnTypeKind = PrimitiveTypeKind | 'e'; + +/** + * A column type that is a primitive type. + */ +export interface PrimitiveColumnType { + type: PrimitiveTypeKind; +} + +/** + * A column type that is an element type. + */ +export interface ElementColumnType { + type: 'e'; + primitiveType: PrimitiveTypeKind; + locationStyle: LocationStyle; + hasLabel: boolean; +} + +/** + * The type of a column. + */ +export type ColumnType = PrimitiveColumnType | ElementColumnType; + +/** + * The schema describing a single column in a `ResultSet`. + */ +export interface ColumnSchema { + readonly name: string; + readonly type: ColumnType; +} + +/** + * The schema of a single `ResultSet` in a BQRS file. + */ +export interface ResultSetSchema { + readonly version: number; + readonly name: string; + readonly tupleCount: number; + readonly columns: readonly ColumnSchema[]; +} + +/** + * The schema describing the contents of a BQRS file. + */ +export interface ResultSetsSchema { + readonly version: number, + readonly stringPoolSize: number, + readonly resultSets: readonly ResultSetSchema[] +} diff --git a/lib/semmle-bqrs/src/bqrs.ts b/lib/semmle-bqrs/src/bqrs.ts new file mode 100644 index 000000000..0502f3d62 --- /dev/null +++ b/lib/semmle-bqrs/src/bqrs.ts @@ -0,0 +1,18 @@ +import { ResultSetSchema } from './bqrs-schema'; +import { StreamDigester, ChunkIterator } from 'semmle-io'; +import { parseResultSetsHeader, StringPool, parseResultSetSchema, parseTuples, TupleParser } from './bqrs-parse'; + +export async function parse(rs: ChunkIterator, + resultSetHandler: (resultSet: ResultSetSchema) => TupleParser): Promise { + + const d = StreamDigester.fromChunkIterator(rs); + + const header = await parseResultSetsHeader(d); + const stringPool = new StringPool(await d.read(header.stringPoolSize)); + for (let resultSetIndex = 0; resultSetIndex < header.resultSetCount; resultSetIndex++) { + await d.readLEB128UInt32(); // Length of result set. Unused. + const resultSetSchema = await parseResultSetSchema(d); + const tupleParser = resultSetHandler(resultSetSchema); + await parseTuples(d, resultSetSchema, stringPool, tupleParser); + } +} diff --git a/lib/semmle-bqrs/src/index.ts b/lib/semmle-bqrs/src/index.ts new file mode 100644 index 000000000..1394108b0 --- /dev/null +++ b/lib/semmle-bqrs/src/index.ts @@ -0,0 +1,7 @@ +export * from './bqrs'; +export * from './bqrs-custom'; +export * from './bqrs-file'; +export * from './bqrs-results'; +export * from './bqrs-schema'; +export * from './path-problem-query-results'; +export * from './problem-query-results'; diff --git a/lib/semmle-bqrs/src/path-problem-query-results.ts b/lib/semmle-bqrs/src/path-problem-query-results.ts new file mode 100644 index 000000000..78392899f --- /dev/null +++ b/lib/semmle-bqrs/src/path-problem-query-results.ts @@ -0,0 +1,49 @@ +import 'reflect-metadata'; +import { Element } from './bqrs-results'; +import { qlElement, qlString, qlTuple, qlTable } from './bqrs-custom'; +import { ElementReference } from './problem-query-results'; + +export class PathProblemAlert { + @qlElement(0) + element: Element; + @qlElement(1) + source: Element; + @qlElement(2) + sink: Element; + @qlString(3) + message: string; + @qlTuple({ startColumn: 4 }, ElementReference) + references?: ElementReference[]; +} + +export class PathProblemEdge { + @qlElement(0) + predecessor: Element; + @qlElement(1) + successor: Element; +} + +export class GraphProperty { + @qlString(0) + key: string; + @qlString(1) + value: string; +} + +export class PathProblemNode { + @qlElement(0) + node: Element; + // There can really only be zero or one of these, but until we support optional columns, we'll + // model it as a "rest" property. + @qlTuple({ startColumn: 1 }, GraphProperty) + properties?: GraphProperty[]; +} + +export class PathProblemQueryResults { + @qlTable(PathProblemAlert, { name: ['select', 'problems'] }) + problems: PathProblemAlert[]; + @qlTable(PathProblemNode) + nodes: PathProblemNode[]; + @qlTable(PathProblemEdge) + edges: PathProblemEdge[]; +} diff --git a/lib/semmle-bqrs/src/problem-query-results.ts b/lib/semmle-bqrs/src/problem-query-results.ts new file mode 100644 index 000000000..bf13f8fbd --- /dev/null +++ b/lib/semmle-bqrs/src/problem-query-results.ts @@ -0,0 +1,24 @@ +import 'reflect-metadata'; +import { Element } from './bqrs-results'; +import { qlElement, qlString, qlTuple, qlTable } from './bqrs-custom'; + +export class ElementReference { + @qlElement(0) + element: Element; + @qlString(1) + text: string; +} + +export class ProblemAlert { + @qlElement(0) + element: Element; + @qlString(1) + message: string; + @qlTuple({ startColumn: 2 }, ElementReference) + references?: ElementReference[]; +} + +export class ProblemQueryResults { + @qlTable(ProblemAlert, { name: ['select', 'problems'] }) + problems: ProblemAlert[]; +} diff --git a/lib/semmle-bqrs/tsconfig.json b/lib/semmle-bqrs/tsconfig.json new file mode 100644 index 000000000..d2df609ec --- /dev/null +++ b/lib/semmle-bqrs/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./node_modules/typescript-config/lib.tsconfig.json" +} \ No newline at end of file diff --git a/lib/semmle-io-node/gulpfile.js/index.js b/lib/semmle-io-node/gulpfile.js/index.js new file mode 100644 index 000000000..8d874e2d2 --- /dev/null +++ b/lib/semmle-io-node/gulpfile.js/index.js @@ -0,0 +1,7 @@ +'use strict'; + +require('ts-node').register({}); +const { compileTypeScript, watchTypeScript } = require('build-tasks'); + +exports.default = compileTypeScript; +exports.watchTypeScript = watchTypeScript; diff --git a/lib/semmle-io-node/package.json b/lib/semmle-io-node/package.json new file mode 100644 index 000000000..901c0b483 --- /dev/null +++ b/lib/semmle-io-node/package.json @@ -0,0 +1,32 @@ +{ + "name": "semmle-io-node", + "description": "I/O utilities for the Node.js runtime", + "author": "GitHub", + "private": true, + "version": "0.0.1", + "publisher": "GitHub", + "repository": { + "type": "git", + "url": "https://github.com/github/vscode-codeql" + }, + "main": "./out/index", + "files": [ + "out/**", + "package.json" + ], + "scripts": { + "build": "gulp", + "format": "tsfmt -r" + }, + "dependencies": { + "fs-extra": "^8.1.0", + "semmle-io": "^0.0.1" + }, + "devDependencies": { + "@types/fs-extra": "^8.0.0", + "@types/node": "^12.0.8", + "build-tasks": "^0.0.1", + "typescript-config": "^0.0.1", + "typescript-formatter": "^7.2.2" + } +} \ No newline at end of file diff --git a/lib/semmle-io-node/src/file-reader.ts b/lib/semmle-io-node/src/file-reader.ts new file mode 100644 index 000000000..b27b71c30 --- /dev/null +++ b/lib/semmle-io-node/src/file-reader.ts @@ -0,0 +1,66 @@ +import * as fs from 'fs-extra'; +import { ReadStream } from 'fs-extra'; +import { RandomAccessReader, StreamReader } from 'semmle-io'; + +export class FileReader implements RandomAccessReader { + private _fd?: number; + + private constructor(fd: number) { + this._fd = fd; + } + + public dispose(): void { + if (this._fd !== undefined) { + fs.closeSync(this._fd); + this._fd = undefined; + } + } + + public get fd(): number { + if (this._fd === undefined) { + throw new Error('Object disposed.'); + } + + return this._fd; + } + + public readStream(start?: number, end?: number): StreamReader { + return new FileStreamReader(fs.createReadStream('', { + fd: this.fd, + start: start, + end: end, + autoClose: false + })); + } + + public static async open(file: string): Promise { + const fd: number = await fs.open(file, 'r'); + return new FileReader(fd); // Take ownership + } +} + +class FileStreamReader implements StreamReader { + private _stream?: ReadStream; + + public constructor(stream: ReadStream) { + this._stream = stream; + } + + public [Symbol.asyncIterator](): AsyncIterator { + return this.stream[Symbol.asyncIterator](); + } + + public dispose(): void { + if (this._stream !== undefined) { + this._stream = undefined; + } + } + + private get stream(): ReadStream { + if (this._stream === undefined) { + throw new Error('Object disposed.'); + } + + return this._stream; + } +} diff --git a/lib/semmle-io-node/src/index.ts b/lib/semmle-io-node/src/index.ts new file mode 100644 index 000000000..151745efc --- /dev/null +++ b/lib/semmle-io-node/src/index.ts @@ -0,0 +1 @@ +export * from './file-reader'; diff --git a/lib/semmle-io-node/tsconfig.json b/lib/semmle-io-node/tsconfig.json new file mode 100644 index 000000000..d2df609ec --- /dev/null +++ b/lib/semmle-io-node/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./node_modules/typescript-config/lib.tsconfig.json" +} \ No newline at end of file diff --git a/lib/semmle-io/gulpfile.js/index.js b/lib/semmle-io/gulpfile.js/index.js new file mode 100644 index 000000000..8d874e2d2 --- /dev/null +++ b/lib/semmle-io/gulpfile.js/index.js @@ -0,0 +1,7 @@ +'use strict'; + +require('ts-node').register({}); +const { compileTypeScript, watchTypeScript } = require('build-tasks'); + +exports.default = compileTypeScript; +exports.watchTypeScript = watchTypeScript; diff --git a/lib/semmle-io/package.json b/lib/semmle-io/package.json new file mode 100644 index 000000000..5f14bbe8d --- /dev/null +++ b/lib/semmle-io/package.json @@ -0,0 +1,30 @@ +{ + "name": "semmle-io", + "description": "I/O utilities", + "author": "GitHub", + "private": true, + "version": "0.0.1", + "publisher": "GitHub", + "repository": { + "type": "git", + "url": "https://github.com/github/vscode-codeql" + }, + "main": "./out/index", + "files": [ + "out/**", + "package.json" + ], + "scripts": { + "build": "gulp", + "format": "tsfmt -r" + }, + "dependencies": { + "leb": "^0.3.0" + }, + "devDependencies": { + "@types/node": "^12.0.8", + "build-tasks": "^0.0.1", + "typescript-config": "^0.0.1", + "typescript-formatter": "^7.2.2" + } +} \ No newline at end of file diff --git a/lib/semmle-io/src/digester.ts b/lib/semmle-io/src/digester.ts new file mode 100644 index 000000000..4258b1bb2 --- /dev/null +++ b/lib/semmle-io/src/digester.ts @@ -0,0 +1,303 @@ +import * as leb from 'leb'; + +/** + * digester.ts + * ----------- + * + * A wrapper around node's stream and buffer types to make reading the + * binary formats used by the QL query server a little more uniform + * and convenient. + * + * This works around limitations in using Node streams (whether 'paused' or 'flowing') + * with async/await. This code can be simplified if there is a convenient library for doing this. + */ + +export type ChunkIterator = AsyncIterable; + +function endOfStreamError(): Error { + return new Error('Attempt to read past end of stream.'); +} + +const emptyBuffer = Buffer.alloc(0); + +/** + * A class to read and decode bytes out of a sequence of `Buffer`s provided by an async iterator. + */ +export class StreamDigester { + private static readonly MIN_SEAM_BUFFER_LENGTH = 256; + + private currentChunk = emptyBuffer; + private seamBuffer = emptyBuffer; + private done = false; + private positionOfCurrentChunk = 0; + private offsetInCurrentChunk = 0; + private readonly chunks: AsyncIterator; + + private constructor(chunks: ChunkIterator) { + this.chunks = chunks[Symbol.asyncIterator](); + } + + /** + * Create a `StreamDigester`. + * + * @param chunks An async iterator that provides the sequence of buffers from which to read. + */ + public static fromChunkIterator(chunks: ChunkIterator): StreamDigester { + return new StreamDigester(chunks); + } + + public static fromBuffer(buffer: Buffer): StreamDigester { + return new StreamDigester(StreamDigester.singleChunkIterator(buffer)); + } + + public get position(): number { + return this.positionOfCurrentChunk + this.offsetInCurrentChunk; + } + + private static async* singleChunkIterator(chunk: Buffer): AsyncIterableIterator { + yield chunk; + } + + /** + * Gets the next chunk from the iterator, throwing an exception if there are no more chunks + * available. + */ + private async readNextChunk(): Promise { + if (this.done) { + throw endOfStreamError(); + } + + const { value, done } = await this.chunks.next(); + if (done) { + this.done = true; + throw endOfStreamError(); + } + + this.positionOfCurrentChunk += this.currentChunk.length; + this.currentChunk = Buffer.from(value); + this.offsetInCurrentChunk = 0; + } + + private get bytesLeftInCurrentChunk(): number { + return this.currentChunk.length - this.offsetInCurrentChunk; + } + + private getSeamBuffer(byteCount: number, previousBuffer: Buffer, previousOffset: number, + previousByteCount: number): Buffer { + + if (this.seamBuffer.length < byteCount) { + // Start at double the current length, or `MIN_SEAM_BUFFER_LENGTH`, whichever is larger. + let newSeamBufferLength = Math.max(this.seamBuffer.length * 2, + StreamDigester.MIN_SEAM_BUFFER_LENGTH); + while (newSeamBufferLength < byteCount) { + newSeamBufferLength *= 2; + } + + this.seamBuffer = Buffer.alloc(newSeamBufferLength); + } + if (previousByteCount > 0) { + if (previousBuffer === this.seamBuffer) { + if (previousOffset !== 0) { + previousBuffer.copyWithin(0, previousOffset, previousOffset + previousByteCount); + } + } + else { + previousBuffer.copy(this.seamBuffer, 0, previousOffset, previousOffset + previousByteCount); + } + } + + return this.seamBuffer; + } + + private async fillBuffer(buffer: Buffer, start: number, end: number): Promise { + let destOffset = start; + do { + const bytesToCopy = Math.min(end - destOffset, this.bytesLeftInCurrentChunk); + this.currentChunk.copy(buffer, destOffset, this.offsetInCurrentChunk, + this.offsetInCurrentChunk + bytesToCopy); + this.offsetInCurrentChunk += bytesToCopy; + destOffset += bytesToCopy; + if (destOffset < end) { + await this.readNextChunk(); + } + } while (destOffset < end); + } + + /** + * Implements an async read that span multple buffers. + * + * @param canReadFunc Callback function to determine how many bytes are required to complete the + * read operation. + * @param readFunc Callback function to read the requested data from a `Buffer`. + */ + private async readAcrossSeam( + canReadFunc: (buffer: Buffer, start: number, byteCount: number) => number, + readFunc: (buffer: Buffer, offset: number) => T): Promise { + + // We'll copy the leftover bytes from the current chunk, plus whatever bytes we need from + // subsequent chunks, into a "seam buffer", and read the value from there. + let buffer = this.currentChunk; + let offsetInBuffer = this.offsetInCurrentChunk; + let discardedBytes = 0; + let bytesInBuffer = this.bytesLeftInCurrentChunk; + while (true) { + // Ask how many bytes we need to complete the read. + const requestedBytes = canReadFunc(buffer, offsetInBuffer, bytesInBuffer); + if (requestedBytes <= bytesInBuffer) { + // We have enough bytes. Do the read. + const value = readFunc(buffer, offsetInBuffer); + this.offsetInCurrentChunk += requestedBytes - discardedBytes; + return value; + } + + // We've already copied all the bytes from our current chunk to the seam buffer. We're + // guaranteed to wind up reading all of those bytes, and will need at least one more byte, so + // get the next chunk. + await this.readNextChunk(); + + // Create or extend our seam buffer to hold the additional bytes we're about to read. + const bytesToCopy = Math.min(requestedBytes - bytesInBuffer, this.bytesLeftInCurrentChunk); + buffer = this.getSeamBuffer(bytesInBuffer + bytesToCopy, buffer, offsetInBuffer, bytesInBuffer); + discardedBytes = bytesInBuffer; + offsetInBuffer = 0; + + // Append the new bytes to our seam buffer. + this.currentChunk.copy(buffer, bytesInBuffer, 0, bytesToCopy); + bytesInBuffer += bytesToCopy; + } + } + + private readVariableSize( + canReadFunc: (buffer: Buffer, start: number, byteCount: number) => number, + readFunc: (buffer: Buffer, offset: number) => T): Promise { + + const requestedBytes = canReadFunc(this.currentChunk, this.offsetInCurrentChunk, + this.bytesLeftInCurrentChunk); + if (requestedBytes <= this.bytesLeftInCurrentChunk) { + const value = readFunc(this.currentChunk, this.offsetInCurrentChunk); + this.offsetInCurrentChunk += requestedBytes; + return Promise.resolve(value); + } + else { + return this.readAcrossSeam(canReadFunc, readFunc); + } + } + + private readKnownSizeAcrossSeam(byteCount: number, + readFunc: (buffer: Buffer, offset: number) => T): Promise { + + return this.readAcrossSeam((buffer, offset, availableByteCount) => byteCount, readFunc); + } + + private readKnownSize(byteCount: number, readFunc: (buffer: Buffer, offset: number) => T): + Promise { + + if (this.bytesLeftInCurrentChunk >= byteCount) { + // We have enough data. Just read it directly. + const value = readFunc(this.currentChunk, this.offsetInCurrentChunk); + this.offsetInCurrentChunk += byteCount; + return Promise.resolve(value); + } + else { + return this.readKnownSizeAcrossSeam(byteCount, readFunc); + } + } + + /** + * Read a leb128-encoded unsigned 32-bit number + * [https://en.wikipedia.org/wiki/LEB128] + */ + public readLEB128UInt32(): Promise { + return this.readVariableSize(canDecodeLEB128UInt32, decodeLEB128UInt32); + } + + /** + * Read a single byte. + */ + public readByte(): Promise { + return this.readKnownSize(1, (buffer, offset) => buffer[offset]); + } + + /** + * Read a single ASCII character as a string. + */ + public async readASCIIChar(): Promise { + return String.fromCodePoint(await this.readByte()); + } + + /** + * Read the specified number of bytes. + * + * @param byteCount Number of bytes to read. + */ + public async read(byteCount: number): Promise { + const buffer = Buffer.alloc(byteCount); + await this.fillBuffer(buffer, 0, byteCount); + + return buffer; + } + + /** + * Read a `Date` encoded as an 8-byte sequence. + */ + public readDate(): Promise { + return this.readKnownSize(8, decodeDate); + } + + /** + * Read a little-endian 64-bit IEEE floating-point number. + */ + public readDoubleLE(): Promise { + return this.readKnownSize(8, (buffer, offset) => buffer.readDoubleLE(offset)); + } + + /** + * Read a UTF-8 encoded string. + * @param byteCount Length of encoded string in bytes. + */ + public readUTF8String(byteCount: number): Promise { + return this.readKnownSize(byteCount, (buffer, offset) => + buffer.toString('utf8', offset, offset + byteCount)); + } +} + +function decodeDate(buffer: Buffer, offset: number): Date { + const low = buffer.readUInt32LE(offset); + const high = buffer.readUInt32LE(offset + 4); + + const year = (high & 0x1ffffff0) >> 4; + const month = high & 0x0000000f; + const day = (low & 0xf8000000) >>> 27; + const hours = (low & 0x07c00000) >> 22; + const minutes = (low & 0x003f0000) >> 16; + const seconds = (low & 0x0000fc00) >> 10; + const ms = low & 0x000003ff; + + return new Date(year, month, day, hours, minutes, seconds, ms); +} + +/** + * The longest possible byte length of a correctly encoded LEB128 UInt32: + * `0xff 0xff 0xff 0xff 0x8f` (5 bytes) + */ +const MAX_ENCODED_UINT32_LENGTH = 5; + +function canDecodeLEB128UInt32(buffer: Buffer, offset: number, byteCount: number): number { + const endOffset = offset + Math.min(byteCount, MAX_ENCODED_UINT32_LENGTH); + for (let byteOffset = offset; byteOffset < endOffset; byteOffset++) { + if ((buffer[byteOffset] & 0x80) === 0) { + return (byteOffset - offset) + 1; + } + } + + if ((endOffset - offset) > MAX_ENCODED_UINT32_LENGTH) { + throw new Error('Invalid LEB128 encoding.') + } + + return MAX_ENCODED_UINT32_LENGTH; +} + +function decodeLEB128UInt32(buffer: Buffer, offset: number): number { + const { value } = leb.decodeUInt32(buffer, offset); + return value; +} \ No newline at end of file diff --git a/lib/semmle-io/src/index.ts b/lib/semmle-io/src/index.ts new file mode 100644 index 000000000..86ce069e7 --- /dev/null +++ b/lib/semmle-io/src/index.ts @@ -0,0 +1,2 @@ +export * from './digester'; +export * from './random-access-reader'; diff --git a/lib/semmle-io/src/random-access-reader.ts b/lib/semmle-io/src/random-access-reader.ts new file mode 100644 index 000000000..68734b692 --- /dev/null +++ b/lib/semmle-io/src/random-access-reader.ts @@ -0,0 +1,8 @@ +export interface StreamReader extends AsyncIterable { + dispose(): void; +} + +export interface RandomAccessReader { + readStream(start?: number, end?: number): StreamReader; + dispose(): void; +} diff --git a/lib/semmle-io/tsconfig.json b/lib/semmle-io/tsconfig.json new file mode 100644 index 000000000..d2df609ec --- /dev/null +++ b/lib/semmle-io/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./node_modules/typescript-config/lib.tsconfig.json" +} \ No newline at end of file diff --git a/lib/semmle-vscode-utils/gulpfile.js/index.js b/lib/semmle-vscode-utils/gulpfile.js/index.js new file mode 100644 index 000000000..8d874e2d2 --- /dev/null +++ b/lib/semmle-vscode-utils/gulpfile.js/index.js @@ -0,0 +1,7 @@ +'use strict'; + +require('ts-node').register({}); +const { compileTypeScript, watchTypeScript } = require('build-tasks'); + +exports.default = compileTypeScript; +exports.watchTypeScript = watchTypeScript; diff --git a/lib/semmle-vscode-utils/package.json b/lib/semmle-vscode-utils/package.json new file mode 100644 index 000000000..10fc82d2e --- /dev/null +++ b/lib/semmle-vscode-utils/package.json @@ -0,0 +1,26 @@ +{ + "name": "semmle-vscode-utils", + "description": "Shared utilities for writing Visual Studio Code extensions", + "author": "GitHub", + "private": true, + "version": "0.0.1", + "publisher": "GitHub", + "repository": { + "type": "git", + "url": "https://github.com/github/vscode-codeql" + }, + "main": "./out/index", + "scripts": { + "build": "gulp", + "format": "tsfmt -r" + }, + "devDependencies": { + "@types/node": "^12.0.8", + "@types/vscode": "^1.39.0", + "build-tasks": "^0.0.1", + "typescript": "^3.5.2", + "typescript-config": "^0.0.1", + "typescript-formatter": "^7.2.2" + }, + "dependencies": {} +} \ No newline at end of file diff --git a/lib/semmle-vscode-utils/src/disposable-object.ts b/lib/semmle-vscode-utils/src/disposable-object.ts new file mode 100644 index 000000000..33c2d0987 --- /dev/null +++ b/lib/semmle-vscode-utils/src/disposable-object.ts @@ -0,0 +1,63 @@ +import { Disposable } from "vscode"; + +/** + * Base class to make it easier to implement a `Disposable` that owns other disposable object. + */ +export abstract class DisposableObject implements Disposable { + private disposables: Disposable[] = []; + private tracked?: Set = undefined; + + constructor() { + } + + /** + * Adds `obj` to a list of objects to dispose when `this` is disposed. Objects added by `push` are + * disposed in reverse order of being added. + * @param obj The object to take ownership of. + */ + protected push(obj: T): T { + if (obj !== undefined) { + this.disposables.push(obj); + } + return obj; + } + + /** + * Adds `obj` to a set of objects to dispose when `this` is disposed. Objects added by + * `track` are disposed in an unspecified order. + * @param obj The object to track. + */ + protected track(obj: T): T { + if (obj !== undefined) { + if (this.tracked === undefined) { + this.tracked = new Set(); + } + this.tracked.add(obj); + } + return obj; + } + + /** + * Removes `obj`, which must have been previously added by `track`, from the set of objects to + * dispose when `this` is disposed. `obj` itself is disposed. + * @param obj The object to stop tracking. + */ + protected disposeAndStopTracking(obj: Disposable): void { + if (obj !== undefined) { + this.tracked!.delete(obj); + obj.dispose(); + } + } + + public dispose() { + if (this.tracked !== undefined) { + for (const trackedObject of this.tracked.values()) { + trackedObject.dispose(); + } + this.tracked = undefined; + } + while (this.disposables.length > 0) { + this.disposables.pop()!.dispose(); + } + } +} diff --git a/lib/semmle-vscode-utils/src/index.ts b/lib/semmle-vscode-utils/src/index.ts new file mode 100644 index 000000000..a583c6ec2 --- /dev/null +++ b/lib/semmle-vscode-utils/src/index.ts @@ -0,0 +1 @@ +export * from './disposable-object'; diff --git a/lib/semmle-vscode-utils/tsconfig.json b/lib/semmle-vscode-utils/tsconfig.json new file mode 100644 index 000000000..d2df609ec --- /dev/null +++ b/lib/semmle-vscode-utils/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./node_modules/typescript-config/lib.tsconfig.json" +} \ No newline at end of file diff --git a/rush.json b/rush.json new file mode 100644 index 000000000..3ab8b8116 --- /dev/null +++ b/rush.json @@ -0,0 +1,58 @@ +/** + * This is the main configuration file for Rush. + * For full documentation, please see https://rushjs.io/pages/configs/rush_json/ + */ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush.schema.json", + "rushVersion": "5.11.2", + "pnpmVersion": "2.15.1", + "pnpmOptions": { + "strictPeerDependencies": true, + }, + "nodeSupportedVersionRange": ">=10.13.0 <13.0.0", + "ensureConsistentVersions": true, + "projectFolderMinDepth": 2, + "projectFolderMaxDepth": 2, + "gitPolicy": {}, + "repository": { + "url": "https://github.com/github/vscode-codeql" + }, + "eventHooks": { + "preRushInstall": [], + "postRushInstall": [], + "preRushBuild": [], + "postRushBuild": [] + }, + "variants": [], + "projects": [ + { + "packageName": "typescript-config", + "projectFolder": "configs/typescript-config" + }, + { + "packageName": "build-tasks", + "projectFolder": "tools/build-tasks" + }, + { + "packageName": "semmle-bqrs", + "projectFolder": "lib/semmle-bqrs" + }, + { + "packageName": "semmle-io", + "projectFolder": "lib/semmle-io" + }, + { + "packageName": "semmle-io-node", + "projectFolder": "lib/semmle-io-node" + }, + { + "packageName": "semmle-vscode-utils", + "projectFolder": "lib/semmle-vscode-utils" + }, + { + "packageName": "vscode-codeql", + "projectFolder": "extensions/ql-vscode", + "shouldPublish": true + } + ] +} \ No newline at end of file diff --git a/tools/build-tasks/gulpfile.js/index.js b/tools/build-tasks/gulpfile.js/index.js new file mode 100644 index 000000000..95cf8bcb1 --- /dev/null +++ b/tools/build-tasks/gulpfile.js/index.js @@ -0,0 +1,7 @@ +'use strict'; + +require('ts-node').register({}); +const { compileTypeScript, watchTypeScript } = require('../src/index'); + +exports.default = compileTypeScript; +exports.watchTypeScript = watchTypeScript; diff --git a/tools/build-tasks/package.json b/tools/build-tasks/package.json new file mode 100644 index 000000000..ac5f4ce25 --- /dev/null +++ b/tools/build-tasks/package.json @@ -0,0 +1,47 @@ +{ + "name": "build-tasks", + "description": "Internal Gulp tasks", + "author": "GitHub", + "private": true, + "version": "0.0.1", + "publisher": "GitHub", + "repository": { + "type": "git", + "url": "https://github.com/github/vscode-codeql" + }, + "main": "./out/index", + "scripts": { + "build": "gulp", + "format": "tsfmt -r" + }, + "dependencies": { + "@microsoft/node-core-library": "~3.13.0", + "@microsoft/rush-lib": "~5.11.2", + "ansi-colors": "^4.0.1", + "child-process-promise": "^2.2.1", + "fs-extra": "^8.1.0", + "glob-promise": "^3.4.0", + "gulp": "^4.0.2", + "gulp-sourcemaps": "^2.6.5", + "gulp-typescript": "^5.0.1", + "js-yaml": "^3.12.0", + "jsonc-parser": "~2.1.0", + "npm-packlist": "~1.4.4", + "plugin-error": "^1.0.1", + "through2": "^3.0.1", + "vinyl": "^2.2.0" + }, + "devDependencies": { + "@types/child-process-promise": "^2.2.1", + "@types/fs-extra": "^8.0.0", + "@types/gulp": "^4.0.6", + "@types/js-yaml": "~3.12.1", + "@types/node": "^12.0.8", + "@types/npm-packlist": "~1.1.1", + "@types/through2": "~2.0.34", + "@types/vinyl": "~2.0.3", + "typescript": "^3.5.2", + "typescript-config": "^0.0.1", + "typescript-formatter": "^7.2.2" + } +} \ No newline at end of file diff --git a/tools/build-tasks/src/deploy.ts b/tools/build-tasks/src/deploy.ts new file mode 100644 index 000000000..6b40a6e2a --- /dev/null +++ b/tools/build-tasks/src/deploy.ts @@ -0,0 +1,189 @@ +import * as fs from 'fs-extra'; +import * as jsonc from 'jsonc-parser'; +import { IPackageJson } from '@microsoft/node-core-library'; +import * as path from 'path'; +import { getRushContext, RushContext } from './rush'; +import * as packlist from 'npm-packlist'; +import * as glob from 'glob-promise'; +import * as cpp from 'child-process-promise'; + +interface IPackageInfo { + name: string; + version: string; + sourcePath: string; + files: string[]; + dependencies: IPackageInfo[]; + isRoot?: boolean; + copied?: boolean; +} + +async function copyPackage(packageFiles: IPackageInfo, destPath: string): Promise { + for (const file of packageFiles.files) { + await fs.copy(path.resolve(packageFiles.sourcePath, file), path.resolve(destPath, file)); + } +} + +export interface DeployedPackage { + distPath: string; + name: string; + version: string; +} + +class PackageMap { + private map = new Map>(); + + constructor() { + } + + public getPackageInfo(name: string, version: string): IPackageInfo | undefined { + const versionMap = this.map.get(name); + if (versionMap === undefined) { + return undefined; + } + + return versionMap.get(version); + } + + public addPackageInfo(pkg: IPackageInfo): void { + if (this.getPackageInfo(pkg.name, pkg.version)) { + throw new Error(`Attempt to add duplicate package '${pkg.name}@${pkg.version}'.`); + } + + let versionMap = this.map.get(pkg.name); + if (versionMap === undefined) { + versionMap = new Map(); + this.map.set(pkg.name, versionMap); + } + + versionMap.set(pkg.version, pkg); + } + + public hasMultipleVersions(name: string): boolean { + return this.map.get(name)!.size > 1; + } +} + +async function collectPackages(context: RushContext, name: string, version: string, + pkgs: PackageMap): Promise { + + let pkg = pkgs.getPackageInfo(name, version); + if (!pkg) { + const info = await context.getPackageInfo(name, version); + + let files: string[]; + if (info.isLocal) { + // For local packages, use `packlist` to get the list of files that npm would have packed + // into the tarball. + files = packlist.sync({ path: info.path }); + } + else { + // For non-local packages, just copy everything. + files = await glob('**/*', { + nodir: true, + cwd: info.path + }); + } + + pkg = { + name: name, + version: version, + sourcePath: info.path, + files: files, + dependencies: [] + }; + + pkgs.addPackageInfo(pkg); + + for (const dependencyName of info.dependencies.keys()) { + const dependencyVersion = info.dependencies.get(dependencyName)!; + + const dependencyPackage = await collectPackages(context, dependencyName, dependencyVersion, pkgs); + pkg.dependencies.push(dependencyPackage); + } + } + + return pkg; +} + +async function copyPackageAndModules(pkg: IPackageInfo, pkgs: PackageMap, destPath: string, + rootNodeModulesPath: string): Promise { + + let destPackagePath: string; + if (pkgs.hasMultipleVersions(pkg.name) || pkg.isRoot) { + // Copy as a nested package, and let `npm dedupe` fix it up later if possible. + destPackagePath = path.join(destPath, pkg.name); + } + else { + // Copy to the root `node_modules` directory. + if (pkg.copied) { + return; + } + pkg.copied = true; + destPackagePath = path.join(rootNodeModulesPath, pkg.name); + } + + await copyPackage(pkg, destPackagePath); + const nodeModulesPath = path.join(destPackagePath, 'node_modules'); + for (const dependencyPkg of pkg.dependencies) { + await copyPackageAndModules(dependencyPkg, pkgs, nodeModulesPath, rootNodeModulesPath); + } +} + +export async function deployPackage(packageJsonPath: string): Promise { + try { + const context = await getRushContext(path.dirname(packageJsonPath)); + + const rootPackage: IPackageJson = jsonc.parse(await fs.readFile(packageJsonPath, 'utf8')); + + // Default to development build; use flag --release to indicate release build. + const isDevBuild = !process.argv.includes('--release'); + const distDir = path.join(context.rushConfig.rushJsonFolder, 'dist'); + await fs.mkdirs(distDir); + + if (isDevBuild) { + // NOTE: rootPackage.name had better not have any regex metacharacters + const oldDevBuildPattern = new RegExp('^' + rootPackage.name + '[^/]+-dev\\d+.vsix$'); + // Dev package filenames are of the form + // vscode-codeql-0.0.1-dev20190927195520723.vsix + fs.readdirSync(distDir).filter(name => name.match(oldDevBuildPattern)).map(build => { + console.log(`Deleting old dev build ${build}...`); + fs.unlinkSync(path.join(distDir, build)); + }); + rootPackage.version = rootPackage.version + '-dev' + new Date().toISOString().replace(/[^0-9]/g, ''); + } + + const distPath = path.join(distDir, rootPackage.name); + await fs.remove(distPath); + await fs.mkdirs(distPath); + + console.log(`Gathering transitive dependencies of package '${rootPackage.name}'...`); + const pkgs = new PackageMap(); + const rootPkg = await collectPackages(context, rootPackage.name, rootPackage.version, pkgs); + rootPkg.isRoot = true; + + console.log(`Copying package '${rootPackage.name}' and its dependencies to '${distPath}'...`); + await copyPackageAndModules(rootPkg, pkgs, path.dirname(distPath), path.join(distPath, 'node_modules')); + await fs.copy(path.resolve(rootPkg.sourcePath, ".vscodeignore"), path.resolve(distPath, ".vscodeignore")); + + console.log(`Deduplicating dependencies of package '${rootPackage.name}'...`); + // We create a temporary `package-lock.json` file just to prevent `npm ls` from printing out the + // message that it created a package-lock.json. + const packageLockPath = path.join(distPath, 'package-lock.json'); + await fs.writeFile(packageLockPath, '{}'); + await cpp.spawn('npm', ['dedupe'], { + cwd: distPath, + stdio: 'inherit' + }); + await fs.unlink(packageLockPath); + + return { + distPath: distPath, + name: rootPackage.name, + version: rootPackage.version + }; + } + catch (e) { + console.error(e); + throw e; + } +} diff --git a/tools/build-tasks/src/index.ts b/tools/build-tasks/src/index.ts new file mode 100644 index 000000000..2ea9c3656 --- /dev/null +++ b/tools/build-tasks/src/index.ts @@ -0,0 +1,4 @@ +export * from './package'; +export * from './textmate'; +export * from './typescript'; +export * from './tests'; diff --git a/tools/build-tasks/src/package.ts b/tools/build-tasks/src/package.ts new file mode 100644 index 000000000..10fd9095b --- /dev/null +++ b/tools/build-tasks/src/package.ts @@ -0,0 +1,23 @@ +import * as path from 'path'; +import { deployPackage } from './deploy'; +import * as child_process from 'child-process-promise'; + +export async function packageExtension(): Promise { + const deployedPackage = await deployPackage(path.resolve('package.json')); + console.log(`Packaging extension '${deployedPackage.name}@${deployedPackage.version}'...`); + const args = [ + 'package', + '--out', path.resolve(deployedPackage.distPath, '..', `${deployedPackage.name}-${deployedPackage.version}.vsix`) + ]; + const proc = child_process.spawn('vsce', args, { + cwd: deployedPackage.distPath + }); + proc.childProcess.stdout!.on('data', (data) => { + console.log(data.toString()); + }); + proc.childProcess.stderr!.on('data', (data) => { + console.error(data.toString()); + }); + + await proc; +} diff --git a/tools/build-tasks/src/pnpm.ts b/tools/build-tasks/src/pnpm.ts new file mode 100644 index 000000000..85cc2b6ca --- /dev/null +++ b/tools/build-tasks/src/pnpm.ts @@ -0,0 +1,17 @@ +export interface PackageDependencies { + [key: string]: string; +} + +export interface ShrinkwrapPackage { + dependencies?: PackageDependencies; + dev?: boolean; + name?: string; + version?: string; +} + +export interface Shrinkwrap { + dependencies: PackageDependencies; + packages: { + [key: string]: ShrinkwrapPackage; + } +} diff --git a/tools/build-tasks/src/rush.ts b/tools/build-tasks/src/rush.ts new file mode 100644 index 000000000..ba67371bf --- /dev/null +++ b/tools/build-tasks/src/rush.ts @@ -0,0 +1,146 @@ +import * as fs from 'fs-extra'; +import * as jsonc from 'jsonc-parser'; +import { Shrinkwrap, ShrinkwrapPackage } from './pnpm'; +import * as path from 'path'; +import { IPackageJson } from '@microsoft/node-core-library'; +import { RushConfiguration } from '@microsoft/rush-lib'; +import * as yaml from 'js-yaml'; + +export interface IPackageJsonWithFiles extends IPackageJson { + files?: string[] +} + +interface PackageInfo { + path: string; + dependencies: Map; + config: IPackageJsonWithFiles; + isLocal: boolean; +} + +const peerDependencyVersionPattern = /^\/((?:@(?:[^\/]+)\/)?[^\/]+)\/([^\/]+)\//; + +export class RushContext { + private shrinkwrap?: Shrinkwrap; + private shrinkwrapPackages?: Map; + private packageRepository: string; + + constructor(public readonly rushConfig: RushConfiguration) { + this.packageRepository = path.join(rushConfig.pnpmStoreFolder, '2/registry.npmjs.org'); + } + + private getRushProjectPath(name: string): string | undefined { + const project = this.rushConfig.getProjectByName(name); + if (project) { + return project.projectFolder; + } + else { + return undefined; + } + } + + private async getShrinkwrap(): Promise { + if (!this.shrinkwrap) { + this.shrinkwrap = yaml.safeLoad(await fs.readFile(this.rushConfig.getCommittedShrinkwrapFilename(), 'utf8')); + } + + return this.shrinkwrap!; + } + + private async getShrinkwrapPackage(name: string, version: string): Promise { + const shrinkwrap = await this.getShrinkwrap(); + + if (!this.shrinkwrapPackages) { + this.shrinkwrapPackages = new Map(); + for (const name in shrinkwrap.packages) { + const pkg = shrinkwrap.packages[name]; + let packageKey: string; + if (pkg.name) { + packageKey = makePackageKey(pkg.name, pkg.version!); + } + else { + packageKey = name; + } + this.shrinkwrapPackages.set(packageKey, pkg); + } + } + + const packageKey = makePackageKey(name, version); + const shrinkwrapPackage = this.shrinkwrapPackages.get(packageKey); + if (!shrinkwrapPackage) { + throw new Error(`Package '${packageKey}' not found in shrinkwrap file.`); + } + return shrinkwrapPackage; + } + + public async getPackageInfo(name: string, version: string): Promise { + let pkg: ShrinkwrapPackage; + const rushProject = this.rushConfig.getProjectByName(name); + let packagePath: string; + let config: IPackageJsonWithFiles; + if (rushProject) { + packagePath = rushProject.projectFolder; + pkg = await this.getShrinkwrapPackage(rushProject.tempProjectName, '0.0.0'); + config = rushProject.packageJson; + } + else { + pkg = await this.getShrinkwrapPackage(name, version); + packagePath = path.join(this.packageRepository, name, version, 'package'); + if (!await fs.pathExists(packagePath)) { + throw new Error(`Package '${name}:${version}' not found in package repository.`); + } + packagePath = await fs.realpath(packagePath); + config = jsonc.parse(await fs.readFile(path.join(packagePath, 'package.json'), 'utf8')); + } + + const dependencies = new Map(); + if (config.dependencies) { + for (const dependencyName in config.dependencies) { + let dependencyVersion: string; + if (await this.getRushProjectPath(dependencyName)) { + dependencyVersion = '0.0.0'; + } + else { + dependencyVersion = pkg.dependencies![dependencyName]; + if (!dependencyVersion) { + throw new Error(`Package '${name}' depends on unresolved package '${dependencyName}'.`); + } + if (dependencyVersion.startsWith('/')) { + // This is a package with a peer dependency. We need to extract the actual package + // version. + const match = dependencyVersion.match(peerDependencyVersionPattern); + if (match) { + if (match[1] !== dependencyName) { + throw new Error(`Mismatch between package name '${dependencyName}' and peer dependency specifier '${dependencyVersion}'.`); + } + dependencyVersion = match[2]; + } + else { + throw new Error(`Invalid peer dependency specifier '${dependencyVersion}'.`); + } + } + } + + dependencies.set(dependencyName, dependencyVersion); + } + } + + return { + path: packagePath, + dependencies: dependencies, + config: config, + isLocal: rushProject !== undefined + }; + } +} + +function makePackageKey(name: string, version: string): string { + return `/${name}/${version}`; +} + +export async function getRushContext(startingFolder?: string): Promise { + const rushConfig = RushConfiguration.loadFromDefaultLocation({ + startingFolder: startingFolder + }); + + return new RushContext(rushConfig); +} diff --git a/tools/build-tasks/src/tests.ts b/tools/build-tasks/src/tests.ts new file mode 100644 index 000000000..a40d64e07 --- /dev/null +++ b/tools/build-tasks/src/tests.ts @@ -0,0 +1,6 @@ +import * as gulp from 'gulp'; + +export function copyTestData() { + return gulp.src('src/vscode-tests/no-workspace/data/**/*') + .pipe(gulp.dest('out/vscode-tests/no-workspace/data')); +} diff --git a/tools/build-tasks/src/textmate.ts b/tools/build-tasks/src/textmate.ts new file mode 100644 index 000000000..e607074d7 --- /dev/null +++ b/tools/build-tasks/src/textmate.ts @@ -0,0 +1,245 @@ +import * as gulp from 'gulp'; +import * as js_yaml from 'js-yaml'; +import * as through from 'through2'; +import * as PluginError from 'plugin-error'; +import * as Vinyl from 'vinyl'; + +/** + * Replaces all rule references with the match pattern of the referenced rule. + * + * @param value Original regex containing rule references. + * @param replacements Map from rule name to match text. + * @returns The new regex after replacement. + */ +function replaceReferencesWithStrings(value: string, replacements: Map): string { + let result = value; + while (true) { + const original = result; + for (const key of replacements.keys()) { + result = result.replace(`(?#${key})`, `(?:${replacements.get(key)})`); + } + if (result === original) { + return result; + } + } +} + +/** + * Gather all macro definitions from the document. + * + * @param yaml The root of the YAML document. + * @returns A map from macro name to replacement text. + */ +function gatherMacros(yaml: any): Map { + const macros = new Map(); + for (var key in yaml.macros) { + macros.set(key, yaml.macros[key]); + } + + return macros; +} + +/** + * Return the match text to be substituted wherever the specified rule is referenced in a regular + * expression. + * + * @param rule The rule whose match text is to be retrieved. + * @returns The match text for the rule. This is either the value of the rule's `match` property, + * or the disjunction of the match text of all of the other rules `include`d by this rule. + */ +function getNodeMatchText(rule: any): string { + if (rule.match !== undefined) { + // For a match string, just use that string as the replacement. + return rule.match; + } + else if (rule.patterns !== undefined) { + const patterns: string[] = []; + // For a list of patterns, use the disjunction of those patterns. + for (var patternIndex in rule.patterns) { + const pattern = rule.patterns[patternIndex]; + if (pattern.include !== null) { + patterns.push('(?' + pattern.include + ')'); + } + } + + return '(?:' + patterns.join('|') + ')'; + } + else { + return '' + } +} + +/** + * Generates a map from rule name to match text. + * + * @param yaml The root of the YAML document. + * @returns A map whose keys are the names of rules, and whose values are the corresponding match + * text of each rule. + */ +function gatherMatchTextForRules(yaml: any): Map { + const replacements = new Map(); + for (var key in yaml.repository) { + const node = yaml.repository[key]; + replacements.set(key, getNodeMatchText(node)); + } + + return replacements; +} + +/** + * Invoke the specified callback function on each rule definition in the file. + * + * @param yaml The root of the YAML document. + * @param action Callback to invoke on each rule. + */ +function visitAllRulesInFile(yaml: any, action: (rule: any) => void) { + visitAllRulesInRuleMap(yaml.patterns, action); + visitAllRulesInRuleMap(yaml.repository, action); +} + +/** + * Invoke the specified callback function on each rule definition in a map or array of rules. + * For rules that have a `patterns` element defined child rules, the children are included in the + * visitation. + * + * @param ruleMap The map or array of rules to visit. + * @param action Callback to invoke on each rule. + */ +function visitAllRulesInRuleMap(ruleMap: any, action: (rule: any) => void) { + for (var key in ruleMap) { + const rule = ruleMap[key]; + if ((typeof rule) === 'object') { + action(rule); + if (rule.patterns !== undefined) { + visitAllRulesInRuleMap(rule.patterns, action); + } + } + } +} + +/** + * Invoke the specified transformation on all match patterns in the specified rule. + * + * @param rule The rule whose matches are to be transformed. + * @param action The transformation to make on each match pattern. + */ +function visitAllMatchesInRule(rule: any, action: (match: any) => any) { + for (var key in rule) { + switch (key) { + case 'begin': + case 'end': + case 'match': + case 'while': + rule[key] = action(rule[key]); + break; + + default: + break; + } + } +} + +/** + * Replace any usage of the specified `beginPattern` or `endPattern` property with the equivalent + * `begin`/`beginCaptures` or `end`/`endCaptures` properties. + * + * @param rule Rule to be transformed. + * @param key Base key of the property to be transformed. + */ +function expandPatternMatchProperties(rule: any, key: 'begin' | 'end') { + const patternKey = key + 'Pattern'; + const capturesKey = key + 'Captures'; + const pattern = rule[patternKey]; + if (pattern !== undefined) { + const patterns: string[] = Array.isArray(pattern) ? pattern : [pattern]; + rule[key] = patterns.map(p => `((?${p}))`).join('|'); + const captures: { [index: string]: any } = {}; + for (const patternIndex in patterns) { + captures[(Number(patternIndex) + 1).toString()] = { + patterns: [ + { + include: patterns[patternIndex] + } + ] + }; + } + rule[capturesKey] = captures; + rule[patternKey] = undefined; + } +} + +/** + * Transform the specified document to produce a TextMate grammar. + * + * @param yaml The root of the YAML document. + */ +function transformFile(yaml: any) { + const macros = gatherMacros(yaml); + visitAllRulesInFile(yaml, (rule) => { + expandPatternMatchProperties(rule, 'begin'); + expandPatternMatchProperties(rule, 'end'); + }); + + // Expand macros in matches. + visitAllRulesInFile(yaml, (rule) => { + visitAllMatchesInRule(rule, (match) => { + if ((typeof match) === 'object') { + for (var key in match) { + return macros.get(key)!.replace('(?#)', `(?:${match[key]})`); + } + throw new Error("No key in macro map.") + } + else { + return match; + } + }); + }); + + yaml.macros = undefined; + + const replacements = gatherMatchTextForRules(yaml); + // Expand references in matches. + visitAllRulesInFile(yaml, (rule) => { + visitAllMatchesInRule(rule, (match) => { + return replaceReferencesWithStrings(match, replacements); + }); + }); + + if (yaml.regexOptions !== undefined) { + const regexOptions = '(?' + yaml.regexOptions + ')'; + visitAllRulesInFile(yaml, (rule) => { + visitAllMatchesInRule(rule, (match) => { + return regexOptions + match; + }); + }); + + yaml.regexOptions = undefined; + } +} + +export function transpileTextMateGrammar() { + return through.obj((file: Vinyl, encoding: string, callback: Function): void => { + if (file.isNull()) { + callback(null, file); + } + else if (file.isBuffer()) { + const buf: Buffer = file.contents; + const yamlText: string = buf.toString('utf8'); + const jsonData: any = js_yaml.safeLoad(yamlText); + transformFile(jsonData); + + file.contents = Buffer.from(JSON.stringify(jsonData, null, 2), 'utf8'); + file.extname = '.json'; + callback(null, file); + } + else { + callback('error', new PluginError('transpileTextMateGrammar', 'Format not supported.')); + } + }); +} + +export function compileTextMateGrammar() { + return gulp.src('syntaxes/*.tmLanguage.yml') + .pipe(transpileTextMateGrammar()) + .pipe(gulp.dest('out/syntaxes')); +} diff --git a/tools/build-tasks/src/typescript.ts b/tools/build-tasks/src/typescript.ts new file mode 100644 index 000000000..ba9857a7f --- /dev/null +++ b/tools/build-tasks/src/typescript.ts @@ -0,0 +1,68 @@ +import * as colors from 'ansi-colors'; +import * as gulp from 'gulp'; +import * as path from 'path'; +import * as sourcemaps from 'gulp-sourcemaps'; +import * as ts from 'gulp-typescript'; +import { RushConfiguration } from '@microsoft/rush-lib'; + +function goodReporter(): ts.reporter.Reporter { + return { + error: (error, typescript) => { + if (error.tsFile) { + console.log('[' + colors.gray('gulp-typescript') + '] ' + colors.red(error.fullFilename + + '(' + (error.startPosition!.line + 1) + ',' + error.startPosition!.character + '): ') + + 'error TS' + error.diagnostic.code + ': ' + typescript.flattenDiagnosticMessageText(error.diagnostic.messageText, '\n')); + } + else { + console.log(error.message); + } + }, + }; +} + +const tsProject = ts.createProject('tsconfig.json'); + +export function compileTypeScript() { + // Find this project's relative directory. Rush already knows this, so just ask. + const packageDir = path.resolve('.'); + const rushConfig = RushConfiguration.loadFromDefaultLocation({ + startingFolder: packageDir + }); + const project = rushConfig.tryGetProjectForPath(packageDir); + if (!project) { + console.error(`Unable to find project for '${packageDir}' in 'rush.json'.`); + throw Error(); + } + + //REVIEW: Better way to detect deployable projects? + // Since extension .js files are deployed to 'dist//out', and libraries are deployed to + // 'dist//node_modules//out'. + const pathToRoot = (path.dirname(project.projectRelativeFolder) === 'extensions') ? + '../../..' : '../../../../..'; + + return tsProject.src() + .pipe(sourcemaps.init()) + .pipe(tsProject(goodReporter())) + .pipe(sourcemaps.mapSources((sourcePath, file) => { + // The source path is kind of odd, because it's relative to the `tsconfig.json` file in the + // `typescript-config` package, which lives in the `node_modules` directory of the package + // that is being built. It starts out as something like '../../../src/foo.ts', and we need to + // strip out the leading '../../../'. + return path.join('a/b/c', sourcePath); + })) + .pipe(sourcemaps.write('.', { + includeContent: false, + sourceRoot: path.join(pathToRoot, project.projectRelativeFolder) + })) + .pipe(gulp.dest('out')); +} + +export function watchTypeScript() { + gulp.watch('src/**/*.ts', compileTypeScript); +} + +/** Copy CSS files for the results view into the output directory. */ +export function copyViewCss() { + return gulp.src('src/view/*.css') + .pipe(gulp.dest('out')); +} diff --git a/tools/build-tasks/tsconfig.json b/tools/build-tasks/tsconfig.json new file mode 100644 index 000000000..d2df609ec --- /dev/null +++ b/tools/build-tasks/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./node_modules/typescript-config/lib.tsconfig.json" +} \ No newline at end of file diff --git a/tsfmt.json b/tsfmt.json new file mode 100644 index 000000000..67c7d8eb1 --- /dev/null +++ b/tsfmt.json @@ -0,0 +1,18 @@ +{ + "indentStyle": 2, + "insertSpaceAfterCommaDelimiter": true, + "insertSpaceAfterSemicolonInForStatements": true, + "insertSpaceBeforeAndAfterBinaryOperators": true, + "insertSpaceAfterConstructor": false, + "insertSpaceAfterKeywordsInControlFlowStatements": true, + "insertSpaceAfterFunctionKeywordForAnonymousFunctions": false, + "insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": false, + "insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false, + "insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": true, + "insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": false, + "insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces": false, + "insertSpaceAfterTypeAssertion": false, + "insertSpaceBeforeFunctionParenthesis": false, + "placeOpenBraceOnNewLineForFunctions": false, + "placeOpenBraceOnNewLineForControlBlocks": false +} \ No newline at end of file

ff&88>uk*XDYHP+QdUlLs$pf`-Rx~&boY!T0 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�&V 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�v-A*4Z?rb+6^?evedW+C_ zlqb)|kAirmz7m$ZsR~Vx(!TMa9Z#2y0RO!*@1DTteTVa3a3@m!s^kE3@{_51M4U&1 z-&cOD&PO@i#E^_${h^zPaigDhRck2+`p@zJ^;yhvip}&#suia4FnB`IVObIQuD4=i zc=(^5jho=5WWF15aK>aawQVC%mr&0y6?gw4pPFx31a#>PS&1XG(L*-eir3C_?ILT{ zJv)zs44SXCwn5XE!)*NvVT5}ubL6*13|L`lOUeyp?~1|f23=8`gwTFT?x2cdD5VOQ zb&M8*5;pB1SNPyT##z*Xv2x%~d&lKAK)`dEmdjMWmTmQ=lWM2QrrK?ljMzpSmL4Wi zIo-BQN8UaTY7lUL)g})u!dC->Wd_hH@lGpdIWP0GWpU+`)z z3wV%(Q`>dxI^ z*oejTC%L99YSJU+r|1{PZ!aj-K}Nie48uawRX<)YjpY{g^!gX=|KNpn53HHcE>VI> zhf@8RoUEwhQMzf;SXr`X+8^$+!)L1f+|(;Y_P* zQW6)K<0$z++w2xJNXm8u=xi~hQXjGv_KZnniqua%=I&cx{$o6;mwC1~ig~Sp(l|*V zg&%DKd{@=vYxWP9iP8ygssNJ3B?Q{TYc;YAR2?&(j9 z+L>mn>-+%!Y_=kGhdNDzShbR$wd(;Re>xZWJPn+#G+wN^7%U(F8`pe1|LD}>4o!ns z4(!Xxn^;_whZ*j}Ej|STb;@wN{Xps=`K<*fDj+4^<%fcH%V)9^jU)i|?9%e|y3!=U zM)V_dvQj>jusiKMjU@8!Mue#zJAk`{QdqC>5OT`%fUUS5d6?POHcdL4oPT8*t4jpJ z_ncZ}GJ%1bnb)GE?%o@f_p5XBf5ah7yTk(Q72TpvTMnDK+0`u);az0lek!7D?@-~X zg}v|#Cf+!d7xGKRq$e#a?w$LQCr$Bj(%07@^a2u}8a+}DKOJ;}T)>(6KAT+(CiTm8 z#Z#W{6B?Y)3eKMoM;KC(-#fU+Fq|ztvD%5fEH$NNx}*xZ(}#IeHb6MIHg)@NT0>{< z0})#z3)DD30k*Ty%F&t%#Y~UY7c>ES0Ko`0RusDNT+)xN4FB1Bjl5$iF=Y+7lfCXHIsmwfR3wg|auD)B~_ zotf-_jXVNcoleaBrhbUm*9QI z$!>?YU!g335v;e zXN^C^&pKsT7L68w-|=X!zF(jtg%1%0$#t+C_sM($N42Kdhg0FbH08F8SB~j4o0m2z zX@giTYLQ4WrNmk&yp4LWENi3>;_tEz0bM@J97@aKhAEy8 z>ZA-!<{kvUM_>#gY}z0y+d1n*tcpO>{k74v`d6b$SZnZjHB?lg|MX<2-nUW8q7^^0 zuvmKT@P1xAU!^fY5J)014I;b8u@#R(h0ilH&dm&L|BqrI;4-s zjvEOAEOx%QkVA7J{w4=nSsy3-u8$T*gnrirSvYgKa>e7CnY_Vw% zKzQ`!T&}YfV($tZ`C+d~3}FGpf`tlL#g&D9)Xtjh81yy|$~AO}L$JKae!;b$F5uZ# zNNJ;7)SVVjgFUV?wNe=9G$ZryGw&7OtleTjgY*6mn2v=%dESwFS{;j8G;RU1U2+>p zw4s!3$}o}M;z4E>D@?No8o3$%<9Xr|{lkKPC;1HKt-d@WW_63@G`BB2U$H!&s3V_2 z7YfDL6!*mEx?yZtrFP|?Mv4RR@G6v3=7;Jvb`S5n+Uvm)oLVlT{!J=0=F;;bjUMAN zJhUGUj6+s(5>wQy%W!-)%Ad@$P;M@T3xqKZAQoPAIkst4xIrlBfU}Ce#BlqYc2KfO z_}fIin;+9UJjAiMA($e?{azUV6bZj3^j&aO|4`^1q+QZ7e2~pSgstqebd*{0t9eRJ z&q$d7%5!RP(G}fWX|XMKXf(fDMZhH(#4|$XEy+jcUNhek}zQy3gYB zZK>7B*>yC0o8BTV!8Bh)>ZKjnRA+2_vP~M_|FLW!_QX90s2dB4Z-BJrC>?Hw#k+#n z4-=V%YO2tkg1{el#87g$8-K!f)+=9a}ox}!%e6vsLo=8`>OKY#pyx15v}Tg z-`*Q?c*SU|v)<#Sqn%2ys(yG5(-)6yI5+-!FsonzK0nV^li9kA9;akHy z3pEcOix3LhnWx;7jc6s1&eu~skzf0M1@V{N(az>&HmEb1p|z#(r(97%Y;|)Y`h{iS zI(mv2W-TP`CV}D9qnsvlBO-;&3Ca?Galml^f2d$dJ!S19-bJ_#@C2|i-S@??Lrq-= z44GbP2BloHep?k|*H6AG0w!cpO%=Uac4JD*8iQvEjCEg^d!*^LLwz)vo=o@p?%%7~ zHlBGF-B@nl*fpG0PWP_sbE9SFr>1jlZ-(V-%j01KUXsx%srzbU!Cl7R+e_#X{el#0 zQU91;?m+iMEYC;1n5byRbK9e0z?0kNy@4s&;MwZ>_{6KHhJb{JId#DKVSAwRoo|ZO zTU0)>nNiGLcLoO{IC7GF7A-4+*t6kwBlx|pLHmEhGwP8dZ&Z-?lI=KeITavCRIup| z7xD7W&Z*sEz?|~hg7z(2>rNg^770Iaa;;*()j4owJN_1vmKk_(UIDCe9Pk%&qy1Sc z9>>Lam3LIGHKYGEPe3Gq_B8RPf!X*#a%jgNv2FkL9lba@7zi5gHk1h1SzE4N5*s;{ zQ=xl5my%-9^#ik(qJ5t4+E2UVh8{kSdmvkJMIfhT6_=YE$4A-c5!;!vZvGvPTQ}~VAA++Gtp+0B=pVy;9hzNk$mqpa$wUj!rMey%Iv%U z|BDFCEoi2j7(R9t`T1D&d*PQ~gO)c~b)2W4&@gkew5-aMYdG>8<;n-&9fkzQcNMqV z-Jd@s`R|_|U~OC^rK;zJP7=uB$#nkPJx(Z*j&mhTzYtQe_km(C_sMV+T@}v?D5N)~ zVvMr8m1ojGfYw1puF~^cv&PO5PBBBj(F*b6@`HyG)dqYi#h5K+pflD>W z_DOmq@xsSZi9iWYuA46hB&L(lhI!d?0#5?!Ha3{bPg#wl(K=;3sl80wx?Q<^#trM& zYh((`*zK`hQVZ=}Df@W>;(;9-FcTtFKDW6q3mX7s&^osjHyT=DZ_!{-~(}#sg8-R8r+h2h+^ZEeXE@*5xHtq z+$x*B9{_i$_fy(mXH&%Gkk8qd^r7(+4#USP3MVBq+(%2oTxEn(`u5oBCzkK<@lmSDH@FXbmo1jL zK23{pJ1_wdrb0ov>7SY3gS0jxBXTUNhE2UbBq*`lop0WmYag?PTSj-dH*4m}1r7nL zh&e#*D!3hfR`oQBesRr)FJo4S_OteJuiILfcQH=h&fT6DqJQ)GSV*ge6JllJdCP*& zFzT#8EMDDnABmQZdiLHI0lI>%W}Gx>^Glp9a2p1?IrR0dNS|*A+uq_~5eIi|M!zDt z{i;~5rFB!II<9`}nr>wQP)gUQo+9Z(wO})x(#sPtf^2PezO0gYzn_rX`M*^NVEzW8 z&k@L@Co>Ywl94hXI7$j`hk?UIuS4-L@LUhk^XGGvANm6x7{!F#EFwqTflUWnMqOZ4 zJ)e8b!{*1L*rh0|B3a08xvU#jG)dyW?BwhbbH?x_->VeOV}C7PZJS3Zr=qp8{U1&1 z=(>eXi+as{9qF?_c?_EDJITg&dQw>ZCoPimFwhqDJx1t*t1tK`WThrvDQ_5zEvJ-L zC4cl|RlCwdj=1#L=kLF(=`U^L(WAqZngZH?ht7uC4yV4~=jMGur(!=nKfm%a0>6HR zo1=|&Io8abFR$<&6-;$K{GDA(KM$b2WZXng4PqbLv~hIPXNE|qJq&OG)4$(V(u?~& z|JvFYB-^I@iJ|QI@Sih1Q8TA~c9-Kf+po=+ORciTp2A30qwzrd=_re7k&pTx8L=NR zI!rE5Q~&m!9!}{KRqvZ3&ukOW!?&$sTo?a@k0vCN_bI(~N*YMtqp|J(>~=wN8PjKJ@S#d%-O zK?uv?58j~b42V6+-U@VvfNddYUM$>g;XJ05?dUaU+2TFek$nD<68FsKKJ=R^Ln0G1 zg9@_+VNVP|JxP4HT+eq?`S`;(W)+`HI-?hw2Dy>mamC8^n0aS}cr~S-_|yE%h{^3U zV>;Z?ZQnQvA|PKjbE7<9_&Y%}$1=MKalN|Q;Z^5sez#i#;4U(Gfyc;lh8 z$8yuJ9Md8r{_Te{XRn&RSe~|zG=B@yq1~poTsUgWl@5MFk9@vXA6Th)Q6+j@4t=jo z@Vy_Daw#msjtl%Ea?x!&8Y;Q8{w!sPc`Q&LO4PqXHG&Gi5mWjGW9~wGJr(1z;#I`HJk2nox^b_EqLP67GIHYmFHV zcAE^JotEcwDE7Po2~Y%@NyKVpBI->nB^z-plG?4GGQ9t_p7Wq@?W`rg^6SfsTUl3i z9^_K4-fCW!W|8A;hR7Ut5GW%ErSoM{roAuYrHGs}X_Y(rKUVwGcC`1*hH3o2a=}xo zvweAc27(>}zE94?0ci=_9 zm8Uc3s7L4f2IUCmO5M_|V&D7Wk2;_uN?VZU2J;H( z+3hS&PgO7IOx(D_ah8^OMr|%2+^=U1B`GJKb>%^?uk|sPtDJ-9PI?x-zdEL`I$e>$ zoSraG6YjYW=Qco?;d=t_yjqgI@--Pud3oX5_nEf?+zQt%)=$A4Bk*h`_cXPR~b7AIPM=;$^XB{i<8_}iN}na*hTUeZdPL0U(wP5Np!WHXFeOqzU`Zp!F+dUT8FGUa-GY<_GjE>;E=wjr&J|Mh!D*~Fx?8cEQ8FWw!o!uoCS z)>+cx`9?{`@z<1;oZ9Wekr3QmHsV38!sOy1R0q7O=Ryi)Im;p1jJ^1ZVol5VZb8o? z&uWc+c$U>BhT=e3JvJ{R@Btxt6W3*}dn2Ah@sbN&`ZGgDa{EkFI*x8G;FzZIUdd$O z!Hpzo6)9ZFgX)V(51>ZVYj-p)O1}&L4)AcJHZY9)kwv82R+ny-nm+0MmLp%GpGP!X zGHUy@_xZ1*L%?u0Q=WT@ipPX8TBQE00ES7usN}$*P$77o)@{r$7rZI!4dOLZK&&t| zEuLPQJ2^Y<5ZCSyao!$%EgYzn?y-l|RIoVNn2m8ne9c2$=>IJ}GNW7QND7sG>-qVy zVFH19RPB@rHb7~$tv#i)Tz|2ICCnYtJigxk;YeuoJn{5~#+ z`GJ*8S%L$_X+0m*w~s57W$MDrE2&|l1zoc*xjY=lnXbvurMC^;dS@#IwZheK9v|2o z`jYw!)v;}9dGlFyK$rSQ)Cx1-jivC6yDAedKlS`yeSBR>907?J{9O5h;(iR@O4QxN zDEfPpy}eJ?c)(vYaF)CcE?|^Kj5)pq7gihyay(|$?PJay%&aax1O`r`t>a-I(3Xm-0E3<-Qxm+rDmD;@_SiJWO<);!A7bHPTmjYvE#@wk2^N>TW^64X4*{1_C>&ED9$V^qnYrTQ;dpN(wzR73Qps5q9CsLPYX->f7K5Aj)-Q3K`u2^TB@=+(O+!1ze~K zs;{8LrOtrYhrWvC+!_K}d^exB){=bEa46WR#EGM)x%gU`8!HS2XZ1AD49PDfcZaIi z5d>#82}#UC6)U)?fv#+_q3yyCu7cWZVEp}TW<+bW{s4qFSOR&l3GjD9i>uJ3RybZ1 zf>K7KP()m!)huvgKL-RLcXbv{k8G#poR?C4Y8H9VE-JJW?Hi^H zrdyJ!1cAQ{aZ$FTPS`C8;kZ~R+?iB)Z{+KVQJx%~wNkG=$5!`4DA^9$`zEOO#2|@2 z%n8ek&n5CxR4AlR=G{R>JuY*0fxTNJR)cH$Oe;oU^p#*m_)^Ge3GdA#<=EgD3^(uk zEVApq5hT1y^Yq_(`@X}F;dh*C0%R6?%gj-=Yb<9>{S6E6h--}{ahY|Y`(Z{6!ThEY z!}lP%B31w`Z35Z1ihc*t4Pmd0|*=3_xlO3>Jr!ZIFvEeTzspE zxgF8sa{{G?jO<;nK`wdlWGV4YjqXG;M;43-@ZM;Qe33VxF>B)Xy6QhWXOkJ8&I%kd zAGGywpWEA=IQ{9Pxvw(3Qm@-o%^OX7FrS*En^=_@brYbU)TM`q4V*Smd z4V7X2)n7I7%eBnSuhM8cg|A$eRhIQ+ruuui`0;AbtML`@YA|7GOd`G?!2(yX71~n0}b?YhMf;(fU1A&y@RN0ZpJkh9erD>HFqW+tLYkr#H zA!_%)J#ebfnX)0VuqUK*cHnI@oOPNv`!d?v0*2B$-NvHc<@F!&5LcM2L1o~7Kasl8 zX1g*Ut&CeR+2zp0Sbed|s*Q_Hb9S-tw{`GgL-|zr=MOm+Ymu^sMO1 zB1`l)wUDXHBTVsVE|09ubI@KLdFqY<-N+7;)a{d3Q^su{(0Ho_eR3Yx@=<19Ye8<&4F=p0I;$A# zds+H)3Egje_bilPI`Xd$RZ$Iv%?554_+ece16JMi_Z~;Zb|hiUb8_W(ZAhUe z4Qimz8@vZwOBh$JS2-3V4s)E?lBzEZ76_MHk9O}XrSLW|s-l{i{Dz0y#*fSts(x6K z`A|;$!jkA(FvI0V`FMKX2!oVzYspl0=)9Sw| z5pRQd`|8w z=JI}#WYfB+Cb*f44)u1Q5)sfvd&N^Oe&Yr^3NPHAxN-iIQqJjP&9PY5C71@l7C4{r z1-X$^hjH#Ca&&$q4nd)M`NkSoPeYbRJyKi5+@z>E8+{d6U_jV27 z9lK|d9uD9hau~{MiE6h+*4^WF@=WdV6(hJM1UAW@sRQN4mXi_^TU-iN%+4;DT2M(B z)Wuts9CjlGRn!RqRT!46t)e@lq0Tuhk6GvcOr)>hq)Z}71TTyQkdiC#aKv5j8vQo zpg_zV2_9W}>)!)F=RV{fnl(#;BXQFQ<(d||n7PTU;xM=9(5$DVIW&?EV)=83kxryz zj75&)*Xk?Q$P?5iWl`OLk2Wi6wVrq z>ihRA_4`HCWD5AX?h{|;FZl}pAFAFw9Ln#3|F$HprYH)bkjh&2ZAST&q!8JcQL==r zNfUBuXjV@ z3nFlU8)!jM`~L=z%Vq=!Vryg!UN?4sBzF2lqCwgPAm(6)+{P~BN$t~Z>3m`&V^RDb zv*hC3zwbWrYWvv{muhh2A<=DA^6`gl1h8ah;QS7dqpLP?-Wgx(FFpp|{g`7hrr&kMmLLz3m%j(?^R6O<2{l$&;1AN{@BjANrXQskJL|2Q zQ+L-REIIwbzSgtppbvL=))_&{;1g<*9hLsRlMpYThmKxD78JItYlXY@Wbr*6N^1g? zcLfoIq+5Y^-Ur{KrSURWf8eTHt6LuQ%ln{<$(H|htvCPwv4r;$&{W?{_^S$_IZ5y!pw z$pGH54_HoPE7PGfZV!sw{|%F=_bYsDt1;cGe!D=blHSo;_0|ZQS7JX%juoA)DGgeD z>jVunwqyqbrxh=Jf2LpAc&t-7xjo!X&A#DKHfy>ac?+KgUH*7p;z+{wNvPRDFxzA4;7-=)_+rib z>ed)Z8!gxC|2iB{mRqw{cB+jS1*V8wF8}{K80#k=F0z;VvN-`z;1Di9X2nQ}4R#%L zG~+Yg8qZ3hB(i@r&CoV%K0p$2q|+lC{&AI`f<@0pI^OS>dW}i#EZLrpc>|u;zsMnJjVzvUMMPnHsCQ3btR^uOxUJKczd;;v$QFY1LHJo zmDJQIsprsCiBk#OoE7`8s_m0NI)K*s)@QiL5up*s!KebQgWw}x$rU{umFJl2&ya+qVS8pXJLSWH%gS!Sa7yS4$PlHuFN3R7`eDjx)@_xL0b_C*PT{`7QD-i`x zm%}!ef}k9-X|Ll?Z-WQU zGC|B9CKh!xDBy*H?>S0r$*j-2xH#Dtud1u3q<^h6)S#5_=GaiaqcijLQAa9X^>0PV zoo}h$cKDwld9p!xPt(K8qIU(Qx*cmF(Y} zYpK{#9efi+3i^!rIY*0CqT@UI&LN^Ygw+|XDVPaXsZ@Y;-tX38Ad;wE(Ed@2(KL{ylM!(%HYnm zB}ug48{ASKYStBn-3~t_#xx4*S;NjFp<^ z$J0VTo(rxI>{I<7F&icz_@L5L6>hg2&DYmuK>ed9Y!GmVGT{ZHclC!axUXluCxzl@AC$1>Z(Grqp=tZ3AO|{X zxi)Z(uh&oSs0?!)(1SkycldT4;xbfRMAi!YOIGZd_mjVWk-(shxS*e;aK~cH{E521ERnf7Rj=7H+<6CvVSj+Vrm3_Oz8cXF;v7kL;PED6_fq`#OCK*gN zT*rUFi?8?S1@jL?!MM6^$tK_=_3tU;{-!_GUiyoF$uf2oMwo;69_M>1<^h>=}u)oQS zO`M2soFI|(4YYHBJafsW`b%>7W(IAeNmLnwTK)Rm$o>qb*b?@d$WpA|#kd{bDLXkMv^ zwlLx;BfjMhvlmwWBgi85wm;0 zU;GbZpd3tycGF|3Z)LPq@}JZy-`~?u{2vbK?)BA7o;Yfm{{_e(y+w@!4VmUgMjO(J zaLB#BNP3oB%M%(aK8bj-Rk)7->*BXCFp7V4|3gV&-4WKn3;@C#Eee^Q>u#Ly_t-5} zsXbs|uL%H~2EBm4Sk7o0tBD zB@rTPz7NO!PGtP*nv!j{9gCUqyL8h}?Zi9F-yiR)9-g}#JaS%!9tm#_kIbF+`Lv$W zf}0;x<@fAebGxdsvQ{azGBRj%A$z6Uq%bPur&H$p8=Wu5VG~0ZPj6%6$(*$uKb{Pd z2l=wGIz69q_1kLyYuTPmf}FZZ{EtH?7K86qmwh+2IE74{{~Tkd0zfN!ni+a@zmO43zv`J9V!A@N)A%{ zi6O&#YMY5DPZT)t2VpC~?%Ws$8=LBrj-3PqvzpYs=`8$~t+oO}_9?RBx|8N*Xhk9t4x7jtH z=xctCrc!d5?@`BhD7n*8n}t6i7LSs#MhZp9#9!v?-A7gp6)W55>VL0MH1!K<KkGGIh6V%5*{N)p+IDC-uCSy`) z=%5Fm`x`4t*^ro~KYYw&72G%gLqmVDY}8&~d&Ib$&-Ppt@144hQ^6ix*N|e1{A{q1QY@jv6pm*RJjoDo^S8 zh`8Bj%|P3hd%K4!CH?^3+e`EX=VrIfeH;BULN9!zMNMzbj**(y&M~|4x!a#O+C#r3 z!0i!~`lmCx&q)&g8LOD!-=dP1#(urv{a^&(1aqE1e!)IC(S{@!zr1;2PeT;JPPZr^ zJ+c*-Uy-wH9z(QbYj4YBsb3`)2?G_9;zDMSgGKR=jzi<0| zc`B>I5$Ux0DCNUgB>YOQ2;rOaa6xcEoC^l~pH$n`valr3l=#yu|7eSv@x(mZMQC(X zi=tOCqK!IA34013^O#<)l=|whzPKmaPlY?jVu`>N0uvewWo}8;?r@dH`OSFaKbuT? zxa%C%4~TacXe4e+j@3Mg$NCltsaW5>@IR^$xXN$&VCs(DRrfS-OR(`@MqOcf^CWX* zJS6lsVlg|R#|Z5B*DO}8Zeq%~tm=%b-9geBDPS353hWWm0a#+qR0*G$)9}R_n#4B;WhzLod730mWMn(a`~R*zL#$vNCl}XGdM= zt7X?x=Gb|mD^p=lZ*^9tTo>0H@{>*>7hhyAi>nf>eSpqCy<+1b@VG+nXXfpZd7Eer)MRKkED4yt>Yc`~i)5P*QxmqS{&-Tk&XOmV z%1l{gE|X6qXd^MKGXpmlkB=RNNpy*$9`~0OOrV&k&h>~#(`IQ%F46Pyb=JE9z5e!w zBzf4dn{Z%8M@5rR))|X+i+nWrC3Z1Slm)Z@-6sjV-B!x?RgNObN57o;hYYz%l4agU zZxIJBHe`eZ(A(0H$2obU?h>NgTBr;J*71E!GWlw0iqG^ouw%Glm9JMcJ$R1MqhXeHyNr{I-CdaSYv+Z|)6%Esy0znG34El*vQBWe}K6 zA*jFe%DZ;t)0mXKRH`b+hjdF2nnKM#!F3Im`L6%-Q(Q;-A*m?LVr#gwyp6 zJbAwNq@I0Lj9T0Udm3+b>0OX0(iD_8<)E{1U}OOhgJ0AtT{k zsfWi&gX#?z{#9MKX}gC@hHT$jzA@Tda8haPXBYok@_DAG4`V0zop>B+lxoUHnL+5D zXNEN~VSD8`(AGFE{z&?;xPY2i%jsvnFShk6+tZxf>9)$L;M{>N4SxM2cNaarz&7c#X0qEs0 zWa)kKn9F9wBkgl&3Z;*7T@6qFR6QW*m^uA~JiMKXu9U_#SE&9!l-_Ys;{G_coDvWO zBZv2DRqTG@J7%?`0hu-NNu+6btH{PK9b9&}gu&A9zNp>HxqK`sgJ0aG=P;iMlIg{y zDFt$>=#6Sj{epD7|oLHe0g#xL=EywgAKDAu8VWv;+8k79#MDh7{WsbCL{5-2v^Yqz9$w$q8nAf{&*KqteZ2`3QzS$sfLqK%9o-yi~!FznLRH3EOso(FC>e#jy zZni*=5vF~++mj_(O&C)j4vvm7Vvad>ZV2xm*haV@Lq0ksZwJ4vp{P3ejOvMp{A2IN zXj~~iLOm~mFm#8}JayT%5K)uel>JkGyxLFS^oj0M-dr-3hPaj|R@~ICk8sPGrLPMU zuK99a@usa^eMN~ZcQJo`Ygsq*VC^p`u}dm}md!iG8N!_I7MP~BOg?w*Raa!#BB;x# z?%GBBAZ=Y{1Dy)<99prz`kx9Oq$29)Fb7C}P0oxThpH`E&S@fhrH8|ekbT^mi2ElV zfIbdoa`a?9tYYzUU;Boe$64pqttQVl%`r0)kq?89IxR$=^#dKPk`0ziUZBB*LY$Px z`;`a7IR{`tCJHfE6O3zBTVLEy{f=pJ9ttcj+KnK$>xl#7HlC`r3Q8cds}{wFuLX0^ zxY`g zQ}{FE2VwT>3DFGds;0s_tV^Bi$wy%Gb#NFy4<$AP-#j=M@u9Y7McK}fw*EV+E>*Jf zr11a2HgB6k^B%~Qb)@pKI;Aha7A4B$=fY$vy0iZN?xl|yg)A-9t z>+p+0D1q|t3mvOJMMdQ90^|W@>W3c(BQgX=g+r}-pUJV9Hev0){~KxRD_Goj(Ew&V zPA{EN_PkWEk;QNFMBS0chhh=ra?@oPAHsi+u6rUyTL~PK2jIWrendJpb8Y=n<4Fz~ z{)SI60@yV|N7aukvVabw{>Z&dTk9v)Vrm0PT;?ODNWS@*g~2_UvTbQ_Ep$s-w#0@j z;3}+p)5s^l`RX^0Pn`)*-%9EHN+x{S;yW?RiRuJcn%WsAgguz4AAzIe81Cd zBcEk_P5=cPigwD57(J+6YQ~`^Tb|6UT8wYn+zIaRopj4sk!G}gA_}0rP{5_kj+PAi zRs1pSDN?;%G)Gmh9=8Z1@rmMaz4ANI-dqfD5^%}~ z8T*BI0rBdccOkgl{2bSOS;A^lp+Q0R`ZgutzQY-_cSznhpdg2;G1x;YRh7O})BFNq z$$C~deQ*JYdj`X$A()vgVo!*PQ2mmxN-djTDbQivssKckxoR;oKQe09^GyEsTcC$;#eba8Suc+(>0lNv;;mzNF&=yYx z+9m&LE$J*ieU**NoP349fe&X%9_DgAXvpOr%@L^%xrjhT6YMaH)vOgyzY()j2%XDv zrXm=SJBMY|Ig|M-a8IxhyH4@2;7Oya}a8j{U2(L&_U{gK=)X z0j;j3sb^DSyoEf;XN!sn%R=v1dHr51Uf|4F`%V_mJHEELhwqHW`t{EK_4n@nHGGC2 zq^RfTa(;>Re}7(%Boz973Q&i@cwS}W_j@9dq$ieuUsF zRlT-+$G-B|_f7#MoyS=?Bj~HIqFX`KT9eYbPv}n*wfZ6Q!VFH1)XZN+qAx+ zuWJ2kbDExXl7HiYT{G*&VK{xnY@b17t;pJnv zcQL1vB+ih9eu+U!#czLhY~lJAa8mX5c*J)#l>?LDk()vM#tQy+rvK_WM3Akbj7w=Z zBKl$#R&2R99zJNXPJ zp4HZi?|0hmj@9N+R64Fdy(|J-DE;-LPLHO!b>cWPv9U*AVKMw1jeNpu(2St~mYPTq#5q~eal8!>()16-5}2L*!e-p!QXk?H~y?`Ta{U8bL{%-;u?a2tymbtB| z-POP(iSFF^&yR!!HM=684Tu_HN(cLw6KWDYQtUP-kVx+!d3k1QX3=v~#i0{Za)s-wui=u^nt;Yk( z#&C`=pjFZf<-y-&7Bb^}=~(7R84aTGJD+9P|LX!U?W`F?T1(WsVUVZ1)v=3k?*;JT zaX3^%{PZWlFnxJk>V#^3neP?cKlvi^XFHBdhSI)UBw7ljJ;o*DPC#A&3HCYO4_lZR zH*sXP6MB(f(AsR0F_?7PI_%@EQtwiA%VlUAtO&AQ%8vlmV_ZZ~=Zv+mBc?!laOjrR zjz%W`%tS`i5!;6rj$D}E+8|?a$(pmkK1g5m?$sMCItlO6?z;O~w#^;pj>@8sy`}D` z7Ja&F*6~h;1Im{Nw>*)~rsLi{E&#lQKt52_QXU(ZpLu(CwCf({Du$f5{^;b30Xz1U zx=^uy8cV#dIPaEZ_vP_J6af9nSFE@3s)qunpo|=Sx2zPcl!UxkBH37@#Lf@`jikB& zpR%+q%1Bnkix6pc-Uq^L&YTE!nz2+72mK_kxna}<+2SM$9|IBnqSWx6tmVt9+U2&i zrQb%QMiTU+JWrGW^q^)Rk3YWu1U_n!T))@%oa*uqQUUD>gZY;B9%Kis}u{aPA`9CYH<4N5{mjbc7cn|7q`)4$a`Ye3 z3`Sc$nPC9L?tf}Wok8(cYc|Vx>&;DKK7^8YvWET;M@@|OpyW-3#Gi^|$jOBSwY`Pz zeQqy%zj=bRKnG{O_VVIK7;hCOA{%^=M@3DKJ!GIBzI3I)zP9=@8DRaesVbYx@fj;< zSh9QOL4TB4ZbGEF6G4uv3@7R#lTCR04hM)2yFN`mm2yqNCE9?uZ~#)!ss2@P!sYwV z_K)LRiq^}A^wA&>g7QEczN8$jnAdXo^opDi}dey9(Aq#v=3^mBrj zU#)2Oy`gY-A^}jdUEG#!uM7ICT^6^vjaQ}&aPs>A4TI&RZeL;>?+OPiTy=kmrDOo6 zrF1BFP`T5U2Ljh2xx>;#>AlJc;H!L;YMj_`pO=PKuHQLJbHqa*KWKig1#I4osbx&G z+5YX-;%PKe`-supTK5)q2uIHuak1i*&_87`a7GV~?*meXexMgn>$bbLPFwQ@iuj1= zoJx>yC5r=UiOWg<8za-PK+gu~fk4d;3c8`wq;tGMnVJqBLx>D6olk__BTSKJ+NUvo zcR=JCwkoyplx8)_0w+Dz^VW0MU?F*whpY10F0~J`054YUCl(Ini(cRR^#t=fo zJs2D#@tGqdx~UK%Ra&2G;c=WgMCSQ&O5N_N9-i%^+b)`&Na~=VSxe<3Xs?%SO zqvWMnz^B8U-<;YOVR1%2(~g>bV)$X3&(b~9ne^R4Q;Z@#Y$%0I{c*B|w^ zK3r}j-daU5Oo;P8w4Wr{^1ua5QlF2tUNDWNk8Qyvf_@|4C5d0-dAs6RYP0Dz%iE?i zOfzuB6gh@A+aZ5D-tWW%^<{z(0Ds2`)#R3a-3|=_Mio~@-=74b_k>Dz?3r<0xILs| zUz<|v3OW1I!SkBkM=P}hBSc?00=3rPdO1kp`-i{pV?$L0u)OXeUD#nHHA<%_Qt~xn z(kA=Go|F^CBDGd3p?QSr0qwL|y>Rhe89#WHh~I8G zYF(%yh4efDvT_}OSC^X@2HPKBDYbrj<;D4 zNgU$r0|f#q+K3}M>d3DtPr#nWOU>JoZt{bjTTvuD<9v~!o)GKQc|0qn0`zBOQp9n) z8euMQ7Zo&s)^v7;o5%ofOHdxp2W+G!4cot>LpxTr;h#lh35DB1W92%PgBGDNM(07; zOA94b1+D^McSn0)L7OwhyZVN0E3(YnaL?l%`MsHPj;YCYuh(gFkW zTDfQClX>Y5fxd?aA6`Lwr@Q*|CTKx}nWbv~w_xqz5BaP43tHZKIdz$)zxE{)fr|l$ zUuQ8l_(jAHzOwvx<`(bxIFV_h|4F8ppf=*m-WQ$f9CGLWZ64Ohyq#oXvzjj;l}w98 zuzy=p2yqwrua7&;7yJK7-;}Afu1X)l?^4LBNe~nA3fNw;4z{Nhg=p_BkKMnhmtniq zb0yRI_BAI{SlLlgN&IXcZhn|OAzg*D`;R$iJn;AXoCy9#>BTP&Kqh*!_7XvKD@*Tu zfL{!IM(N5Le$VBoOS1>>y(;Yi=vhhVo5r9oR3HQu@WA(~Ol2Xlp(E4oZc@2~gXum1 zt4zIOUT0@tulNo&u$2NWYI%^4_)2vUK)<5Z{@&+5G(GLh&+uE0-nfF+4ICcqiE|vO z%QWDyc`)ljD$Y{XY`u@x&O-+#v>YDJQe7;3YZoA2Sl2$ZkqjTPMf;t*)L%ZiL0_bO zBnb@mPs|KN2@E$ksF$HhK5={$2rUT}t;+&~uxcIa^smhr5|fu(Vf`Cq{RS|ZPe}7< z6LJ~3pVPdW1CCs!qK_~9F$0$#Sj70srRbHYUKFvMOeM~M0&#jH_ZNEKy!>K|^=@MR zt2eFmD_7W zrA>Z@e{LC6w6*_{QV~!7bT|h$JvxKX;*be)wW@SJ1;nPPX|uE(YcVOFF|E#~aZNrK zb^nGYPs&On3j2peJ%aALT%_I+^(2a0s?=>?OBcQ%XC8w|0D^hdhoMiJm5y(+^mteRxg57P~XwE!1qe z8R_(A8-TlNcST3wPV(&O3rI=1cl;`IiiSWnw_3kL^}==t^Z5M(hT}r;ltc6MIV*K! zu28Z`D@z}npJ(v|!DvSvy{Ak{q}U_rn}?!X3#~^wTJr0?RJ5g(KW9{FlDL=(;`L=y z1CML*d+oha2kMuxPvn>KWxrv1wK08wUFn4QS=km7^69ZxxhdguIx$<;F=!}Hn^tJU zcCcgKjMij@MYdnNJ`d=fLgZVsHbLRIE(=Bk6pCtD)%!zmfevmEF?&Ewg*cl~Z?08JGND zj>&Ucnl`_ua~Z%)pLR;eU`i~NA3Zb5rXa)QMdBJ4Ln>L0P6P`=X9gg(Q50=Ct@@x* z+Ksj6w=FiE_%=3=8sls|F7t}K{T&bz-zjp;S`X0MuLI@T`d0G--^6cagpco>Q93;< z{f>9CM`3{wR)Mm3^OTXlO86HgYbB8L7W(G7x9u-|RWNzDm_Gz@CBBoD1Xsnvc>>K} zS%-^f3H%Vmo#2UlC0n;{A?qi$&RFiO+29ph^R-g_-v;Dc^dSrJ*#|c~61RT5ed5Wj z!!3+)?Qh4Qm1k-`o}Gu&-^)%kAx}HV>73*D-4<3+FS9=FDE02TT;p_8Lisk%jB=e1 z#E8n9)iOOfVI1o(o^-GlASZN7^Db?-h7|uDzE=UbkydP)n1XGpO${kk z>-ZcvP4P4Gj0Ek=`=5)oopEetqIv#QYnBWai9==^$U_D96GUn$0Ll;(o#@dHnr z^@g%Ya9cU=HD*4;?v^MFUZhDq0eyRM+<-e|)NhhjY zlcsJ@J>8jksWGLZ`zv#fC$9a0RH$&(LsNsi@t<8|X@-&hWnz)S(Hv=PDh}+Q zbxvQua6T-9o%4i*Ui^iMV0sBNWC2gt!qHorD@oR6#1K5}h&Z2zYbd2Mo0tJR^xeY{ z_euA!hg1!Dh!xgN`nHpcI$yc4d#ShrS*T_-=CEESMl)vVs(%hp-Zfys{gBHs^G1&L z;ld>ue{;|!a{oo&+~^qeh{UVUeRG@AfzLoXKFARpbQv3~5aDpcH=`ekt-_g}=RnV% z_;=+|hV6D3*cb5TftLfX(_wVWPBqx~Fx;+{J0p0DGZzf!K8R#KaS7Ko<_?3*3pZGG zlhTc1!MKwG&-IBsH7|X}^p5H{+J7zAaZveLjyb!)ga>V^00Ud&r$)i{n z{rig?>Q-Otxpw|)&=BB*CGk>(OW|wWc;3oXgpr(}(`FKIJ$(EI+Wo}3h48{mQ_Ws) zao8hy%*b9ErSD}#^7yyS%XaSPAXP(}p4pwQsV2D%oXSCa20gI<(6US6lAym!_q*&* zo@0817Ywku1TtqDGFHuTGdIb4uX0WYY&jzXRl1)opA$fx5Zp|&RK>mz(YB@duZs_4 z_YmAg3)f0F@<`AW=!Y0aCoYJGMK>8@Lz|s;4^GcF*H881z=KF5s?N|JE7z0%L6r0YxD{K{Nz zG@3hTiRntEmx8LhU$W#MDOmZJ1gpTCo%DtQQVZ8RyX3jx0} zrG)+_ULz2HB~J=VHl)xN6ffSI+g%vC&OViUSzvqOaZFI@iHS00X-Dxki??c{KM z9-$DF)hz^cSFy^F>u1 zwl@G;irpg0?&p+W?a;o{ zcEGr-bkncLWMk#W$9Z9b@Si2DweV&RF~^tQpl5MoYAOb?Nd0Fqg?kvn<1o_igxcf- zCf3{aByA9L*SzRuhd7zP=ygiV^jlOE?8zBH1xgPQF)e$zdU9PPbrSyjs0!tt1<1A@ z{WmP_a%al>I6}F4i5ZqB%%*zb57w~dV-D1DrY_0}6AT+v6gg90ueZV}0dHlc9V|&K zd8mawU)2~N)r3AuZDoF&U4BnPlm3_B%?I7fP`@YW=1Ap7G8FUfDyv#zC&do;$Yz(A z7*%kK>1M?7mB#(dJ`nXh>WBzgDiuIX15~-Q$EZXwwb%WrE02odOIytZA&eg8k-VQ~ z@?yqNhuIDP|Mc%#4}mgKDV`_wYL3n0N=t~-Q5s88yFjyT3vY*2NjD6_(P^g>4T{;ns$5_@UEIDoUyz{?c@~%-kOP>@ z@r7d%%mIc;hzl77dxiQiByvO+C6e4hQsd5l_$9q*3(Y9T?wZY^o?R;;*ocxUpPW)c zvhu_D_b&IlhdmoIe|_9su(3!OPq`whDgU8+@TNn-%dyVxz7-Hg{H%BQm#opl=&cxF z{UF@dP40CYhlx0NT}d&rJla%PL(=bKLf4MA=BZOv$-e7;<4N^n)KVC;rdc1Y-LP}G z+3G&j{6f=ia|HWkIn5zJ=tx)bkgPMEqzQeqNU;2)pY)H;~!x`Di>rLcFiP_ z>|upxW{teph%^(vhLpogVJ=*b&$@c+HO06Br3x0n>;RIXn%vO@$Jis59 zKN+f`N?uHID7SVp&(_S?aLUT$c^u^Hl&=r6z}$FwN|jeKadBqBgm#nOb5Wq0mE{^V zN?YTu(6SN$?Ia-7$S3IXIX$?b`7k3jy^Fo zGAi00`cr2R=uMIy==vmDVZwTse{4$6Lr2KR@hb^2)7vOFs{9eaZtWE>RQP*p? z60~-Haf9d?prrL)x%#9w(PGlzvr?Mj6IO%G?BF5p@DQXCXb4}!foxA=ZJBVUC3Bg% z`OV9RPHlBWG17pxdoAif`B(DJ1xdts?VAW1SeC-*NwL21hD*BY)rox{3YVF>hYu)O$1eYyT?6oS`9l4mb&$P+^ol^Igdj` zQG?5{tud2%zy5hZfXL)5acZQ*iSzG0$DN+#)pZ_DJF41O zKWX%U@{XQX@u?mR9xjb?o7v72E5;^!E+#=``~Ley)|J*Urx z&VpRT%UHBcan1uOC;XB8%O9tOFPN(laixqNyee%%z;@GygS6Nn1u`bw(_VH9J_;6@ z&-ybx&6JhJDI-6ExBK%p!rR)7Z$w;A{!N}NBIfAv#FP86p{XYNtryu&oe|Lp;FPc&!DB6b)kKx0J*Xk2+Mdno6_= zo_l_~VDBftG?YJgV1t+&QAJz|su(>n)u86bI(4=R*|M&*0F+p7aM@^_DUufitAbDRN_Q zZG8*#5OoAUxVE3AND+)zPavJUs--NMKw#V{11nSKEjhJFPv2}+0b0^>xh$t{sVlf7 z%@@uKsTNC1r2(e}6;u>$w1{~n5@2{E{1x$wve$f5!13C*8SBZjL&NtKEX^7FIM{(J zDcs8z22aP|Se#lpM`VYC(>wcJrLFz2%027#CPq-8DY1SMI168>fYgz>DXSYyV}XHV z%+W%|UQ!HCa*|7Gi*#@UAW*7sQkmHqmva9^Y>WSkZ@}YC7x)@>Zd2-3TZWVGi0VN! zqjTp#k6mtne))>tzh@Dd>R};_CkbQKL?aVv?!m-n|0wkb7QTsgYPlU66d7U&8s$? zGPe32`mJ+Mo|sPTtoWr)pRoNM*E#Ya;qp+XQL&(-rFc?&!jQlSAB2QDE9-Nr=ObvfP+}9uBTm2(3<@W6J zaqQW%ueLPTRg(8Fr-L9$#j1OY6JNE{z1)F83a@fwy%opDD^Jg4Ea!!lzxm?yXAEw5 zq@zBPpxEZ4$BD0hypzY%Li_zg3h3nLZMo(5jCl?gW<(mqKXuj*{B}Fw6pKs7W^srj z5RR1SN2B=JW!x+FO7MqtZ_^WE=#Vc)S2GWMBiAhRzxNC8<9kK5tt*dXBi84oLb#Lm z(wt*p4Dx+7B{oF;Dra8{kGIA_C8_e_1z;WM7xPr>P?zX=+LI8hE0C{B5u}Sk=%yep z%^zo7Hv9rSKAE-5ErOy*6j+hwBa@B>lh)-4J=>linys|I6J&hLl4GIY%=Um^jAl!H zhA<{|;ugD`{>C5e^?+)v?{oCr`K6G&vQv;^i%006G^xXY`S?06suaja`C3lR6K6u@ zXtuT=xn%m}AB2FIc6r;G2BM}_!RXr08bGbm`6yBLc#PEN7__ivgVTjqH20-u6$SI7 zRt|JPvAOd?aj^}uZIFHG#ZuN1+exwX=AH*JP=~(zZySWSI%Tt7U7mM2P8`(-r_6`m zbbpw>kpUt*tj~AWmNym8MI2VJcfL5_cN0y#S4-z&-!v<`VOA2qnErSOZj|X<4E2cXoyK`7^B7J5$a}M6gb@)JuHAk&>GJjb%o=UXY6GvVsv zo#Pun6PleFj`AxWY%PvKW(_qLPHimE^oC+5#VAAV`m2OuiyNk}p65&C^EXG#S`IIz zr)#_}-VWqykr&X)@|>v}*v?mO)yYqQ<4q268A2}{-&OtcIk+mOOziMqM_sjI#!LUZ^X|V=?>RkPGwgtBorumlQRZ{uSiV4D z!jr5ok(*XNN|n<_QvtWU{Is#$H8BAFQN6P}TH!N?Y;N&d;d2+}1<|4FFG}Ukh+ac| zODNneZcdYIbgtkDYKW8K0aa^lOw$MWNPn8987BUBLT=z}GIRvOC%v_z1}9tki?s3v zTPvcz;m!97YsnZ>CGh(#)Aud!Y{bb*-OiDn0SAmcrfMXroH^xX7wp)5Xq?PLbb(ts z4=)Z60npl?98+884+qyQ@{0!~o@s4fGz#&aDmpoe?`B(as4lC|7%5iY=QKyI0`uN? z+|xQA0T(*g)VaW?73$p&h^W6gbvKctbL^(HU}t;dO~ZjU_^IRv7t=ZI>)ST&4{B_^ zw^#Q2wgwS=0TL7;dj@r=zRp2htmv6?N&Yu^WE3m`Rt$S%NRBMu_-}yU9^+$m@K{$Z zRvKoKKjJvsjH|+7qbv+C2d&<<#r6ccu=5BnS;ErrZo5-YIuWa}GnhLU`8 zl5Cz$KXo{XkvIXi7>!04WNkU zr;F3nRPKckLEj$mZUvu`yT1O~Q{@6~Y)0qkkqAt#d|Nwu1&1X0he^qv$iuuW;Iyk(@C+wh)Ou`8w~j@ zg1qo2=+N6cF-OXRHseF{7^U9A1wIS8N~6X;Yo8(;sOWHa9d4_@6;Uwuewn@Fu?}+E@wy*2!C|E7>Fm@(%qot4>W)vU(Np^|){*y91co=g2u4$FSkg!7CnzP7!^$E6% z>oiSIolUgwE&B7USUx&rwJMT05@Vcu; z%wyVLr`D?XU}3?^*Q+KTFf+~AZe@nsXiVw8^eD~Km}42x{D1NE9*%6jVgEK&v{llD z8kKIfwRVwcwN(@?wfEk86-iKS6-8_Bv^7$DZxUOLh^_Wc>>x1`5pTZF^Zefb;Bw_o zoacQW=kX!Zahnxc_FsIY?`i!p^F&h=V4Xlj)f z^~3H_KEKeDTMpm*wM4ytq?1P4rM&&q>#!{4BOg8ATuDZ%@8di8gH0U6L15W%^KftD z&3IVfnn}COd=1yD>W+K>cHr~Ush)IDM$#m?Zz}I5cAMqAs8Pw-+{bjkB|l0?f+Uo` zQX6F^_d0uDtX#iI zvCw`DxN{YeL@I`d{*U$!pjy9cH4z2baF$z)${M`b_g;(ufZDrti<#gPp`V;Tv&VY{ zj;~SdPqc^0OmffBKG$DXiT?_Puv+XJj9qAWllm zfN1Akuy@#l%C4SR^u}op=y|faS_RtbrjJ-^0O&u$o6**EUn{Vi9G87*;wxkEVYAc+ zo*tPpq|u46ms(QzTeD7PyRfcVPJ}oDy$%07eMwo2$Y9PRyN7crF0DBISf5PMi62W& zAk9MdemknIYl*@hHBPP}gsNq|Uj*hl9n{)cq{sh9vopJZ!>Ad)N zo>%}*7LcIu=Gak=q!d?sBb4_j0gTgf+en2oov-E|iO6NBa-Q>`tDuGQK}Y$eszU4o z2F_P3gzW4m8Z$=HO{|)&T`PT05E={M{Mtn~6pF8@I9wN4>)u4@%#8?{ewS|7IXA@&5L08!t=?YrWI|48$k=eYE0Bqw^$lYWxe-m9mb+ z3njy*>T}Gzy(OWRZ)a)&LQs~*W&OENaqtk_${9b9x z6#*qf7kFJf7De_|ue(gWDkTl6-fdYLjG~%2*i*+Z1AppGy@6+tDt2)MI+^8#3@s6I zBs%=b=dZ0!mOpks@-w^a4vBS~<8(U`zT@;6^?OJ@f}!iFV#_%>m3#ZuXM8td#690~ zt5_Hz+NgF1Q2I3`_@IZuCu~P-UH+}uV5zPJNg)BdGs!3Yeen3DDP-1^h0YjLdh|O3 zx|*HL{ck;X{PeP8YK=YL*%E^Vqrr|0^fN7?=pMmcmTuj1cyEexai(N*T;C$gJ;(kQQ1 zi`I@;z#rJTg$_5M z^Zr`B>o25XGjj9?6=h%P;dlYXt@Zl+f43CoqZV_J7;A?0JfTy+_s~(8(={Dj6mSc8^l6)La4)IL z{fqo9x((2U3WCLt`QoQ9ZA)&+rX5>6yYwh&bT@kYy1({gFiB@>E+>9BRR2GwO9A}S zirlfpHgB+M&{%&%E8vmgb(|if%Nw$ae#b3DsB_T{kNs2|AL&0Ni|I#(=tdpa#f~9& zx|id0DR7YFZxcoRpFykW?5rD2`_}APS&XX15c)QRzhtJ^-TJjRK>8Jd%CgV_id4wZ zCYB&!MSC4^`mWO0%66QTev^F=yJ30mp%i4J7`%^dq}MofZZQ!zSH;zPMg(b~$Y@+R zFm=|X@bigxdUYm16dEQ9mFb$8@)X216>droBerfGH>R@Bj5GJ*hw@u7s<+o#+0}G%AT;N_WA?d17l6oSTq6%`4v-E0zvzRyor^Plnr^oINDT8tcdYX%pcdMy3=KZ>{UZFJp^O#;3)JgTQqRD z<1z4OdZNhPlTBTNrPL$J%s^3>E^w0t_nl`V!yG9$=1#VsntyXiq$I8UThI|jV8TSy zi^rnO&HJJKBhoJO7j9C5wW=2U!+`2g+L#1P*n^VKMe5IdLOT8@Ot}7cpPU;LokXa- zqUpt63+qhiF$DiJ5MP~MOCrQ8T=B}}e?0E;a;h%1C@U*UfekJnF_qK~;un{Yu?|T<#{#d2G_+yex%fR(}E^%ENxfw~w z82-7bGCREal-0?w@{{XAMO?d}|ELVp&4acY4Wt?uzj$u?^30=SDKW+N_kD7*@~90B z=1nYumlww*TK;RrceVPt5N51K&BECH<|DIgv2s#{j*M))FmmFuwBJU05O%!svIwSw za&IE-M$lTkIy`4Yf+A6o*<0k+r*WTf2LP6He)0PI@055S;k$ePb1J&uIt!yx{e_(DM2$r&=|8nuQfZ54hvI$vMANj_4VWjTlqwju##M#%ve-_7* zAs5W0aj~fD$CJVMA5cUb{!JUy8}$8mtFE(?UDv-g`}xH;&eS#C__?-#D!9I-I$d^wAlmMvFxKU0 zC!;ri{6D{1&&b2&_kDNS{!?`|^oyl?f6r11jF3OcOQKyATOBc60a{gIKFtvqpBFR% zk$jXMTY9?z$Ja7?dAxpOF@xukryXMymN?W)$qV@76aqHE6wSOwK&;1`AACY*^uLCT z0{kMw!=kljLC~2Gj~jNndJ|*zJUCoth00PH>x|YvqX}=^?uQl{Ocb}WS~w3h(SkG68`c%K9jeO&q|+)LZ@6QC)Tqvw4Xt_QjpI{Utnl3S1{kT?(Xyaou7Rj1fQOLc{k+a`DWQK20l|J zcU&Hz{89{h9e9%GMqP8xSR!h?fq!|2@4{tbwfNp%9qH~L`b!ebTIMO8j?pr?hS5)f;R{TT+k0XBTqtrw%$p30 zmi3J$3Q>~_(P8-iP`0SK)2E_r-nQer0uLr$Jb)k9mAQ{2U!llt z7=Fwc1Fs~FVms&k+v_+B<&p0TyI+CHshDsko#Q>=RZ)@NaVz_Q>eBy+5+m2oH@KRg z#)Th+1s>BYSe!Lc#p6+psno9%os>E=&o5}IRVDtd-^pDrw*z0(_1*S#@<2rLoa2uV z-S5N+mF^!}QF|hLZsYs$X7Hch(PqTGppwg0N{%==A$#Eu#|fTRy>Q$|b5hNvL4XLQU>Gb2DUo$C4AG;#5SMQnuxrYOS^gh9?z^nuXkq9 z=^Na^wX*42ovq;%D1kgcm03?-nIg-bhF|G}7?`)&!=`exa56wTS@kwX<78kIi(1)I zEbis}+Ij2Z)Nsy{XD1)3)LEuY=by=8V?7T8MerrdzHm=$dL_0#gy#SzD)qtVx3R11 z^G3c=-g7Bp&cZS!%r)jP3d6LyG#*s1NCizIYDp9=R3KplezXvL-SC3F;K!owX=WOYyov# z{R#&zumk%qE5v(o3`Z`suV7Tx$_bb57J1Eaf;JS|rwN)9eKq+Lm;l;DiFQLIw@IJ= zNLBCU4wcMonB>UF)ZKvzNh!RLkp zP9^8dPF9CeLY;3`9mjVE-;xiNh-r8a_}S;~*|xK5lxmtG#~5bo8d2|uc^YOVb=~?g zkh9M^?r2c$Zx^d<(13*MnZc3gJ%wVYE5(lMj%F5}>ExYX)1#S2d*%3XAmMZxT;V#u zAL)8BN*ksDA-y$Y}b^!@rVZk&^p5sArH}Kk%<_{kc{HL3W}mJ=zZ`SGwVKUy_TNV*?mLw zPB)Hzv?7mobWmpzO+CB+l^uSen<8zI^u#-#V#}QtnJQ8?Glw z%Lap+xTAX{$h|tfW>$`mPxGA>LW~0RTzi-$v+T9YiSy{vw$Y+P*M{IlZCPwY^ZxEd z+G7793j9%Kox?3H+n(D9K(q7akyQU6fb(k;7!$eS(Rzc4DaqQN~wIJg2taca9XG|@2 zo~wp`Dq8PYzh1~|{^rixJU1vr&-0vl&uM0WL3t`_s>UWT)K{bScbpMq^>=VI@LSQ5 zn3v?@$Yo8Z^F;nyH(8^F427}Q@_zFj!6igWlMA6xAA1{o5EL@39`&i}kW=SUZoJVF zvFjD*F)=KY=Z~N1l;dNZTnJC8q4q;xH=0L^ekZB#Vfbqkav;Xy^%LjaoP_Dl+<4rL zshPU#7qligE|K0M*M>TEmzcw*BTF@*w5v_D+$NDtNhSx6pPS0{#+EJQZm( zojJbBabGjNb84L$3H+B&tV-upCY^&^f3yio+Yf32O88U*KRyfNYuF|xYDHfq?HsuZ zQ#Bu-5H2gzp&PfQ1}z{D6W+kF@wdNrVzf(0y^OCWDWR3=*mcGd@9TCc>^sGPGM$nLJ{K4hTdnJa5Fxq8a$&>^_x6W=BcY}w#w{oU>OTme$I#2Y6S8) zdx{E%k_$1h8xahO_P;9olUGVPilQQl7f_ zl)tuo?iU_n#430l>dF)9qE#-F+eGR_KTcm68txUl?+kX7L0@I7hkX=b4a5|ZDxaN?j7l}aBJ_A< zZoMrTE$?lO&?EBO?D$T6RAQ-ImREn$S#fR$D>SnruM5Sv@=!ewD5KiRwhzM%t!Q$!lRx$=t$UtP@{Nd%shFw(EWIEvVbY+oCGypMlh_LpLTDk1&W zRWGW|vOjr*tXUwwT?7n#6+9q)+f8|UhH@ht4t*G|fO+hlxf&){xyD6_i8b53;Gw&e zPrmhM@w}JLVdla0FQo_8S;O`>uw3^7Bysxd+|6+IrgK(=H`W?&?qzBCw%v`*)VJGr zxXufPbheV^5USe(Qtbc}FPd|1UMWuZz9z;Svg${XxM@^2Z1-|gbATe4Mzgz0c0)?+ z#^!d@@ono&^Uar(A3!z#=(>np@RdRlGfTcG!%nuziIV?4{D6eN2fX|0ux8V1H@_=pUv@#qOhP_n`1{j%A&pyHG^fAWFv<6fWf+@rIXv?fRZH7i*fPW zkb}SJ?Ib?zi`~~VKA}V4Sq3o0YOBB84R&A<9f{AA6bkHI$c|<0uiQz{^T_C=Y&g=o z6DH3w$2m6-<#tM>K+sK>A9;G%D8{A9Hzt_Und>N{M9IwLPk$dQn)o!zy!`PyHk0$d zGv=X%UhR%X1}+@m5YO20D8_BW<2PqS2l|a=wa3)WfP}SGrx7?9ceviei2y2r%8Cep3y$z$WiZg-kVkw;K!T`s^Jvdye6U z{o6I$Pq7?69Ca&Yi<8_x(-|+RYetcc=c_5{$TMoJa?4rK_>g{rVnIgtvduG&tlxTv zaX6JNMZM_%@@X$;=7Xi8!_1R21wet^+0&z!eVhstk1Ps#6p|iS6l?wB&Z!yEFFv`% zGLztO18%JA*Td^3JlSL3+B+@wjr8C7ToNVr)p&OrsRg#Mk^1$02{AC5I;g`I?e=H| zpKHfd`=XZb3dw8YkX^@a?9iX25ca(vBOz!dzSAeds)ENRvwaVECu1H^y0JL7PoVq; zFoAuotP3L6M+F>gg{_ut_Dub?pCv63rC!O%ZgZ9#wDWKX_uZTv&-otRE2CLEn*X^n zk|n-jPZCr#sv4H6__cB8_HDx?FTRNBL4-=u<|XQ1{`=mQha!+$Nf(~}sMfe5*Ey+< ztt_U`m*Wuil_`2!@0H9}x1o{M=egS0z0$`IHxi!85@9W5-VVb4q8HXbWIId{W(}(1ZY#t|=mb5Os@i}8zF-@qRaeFUi1=6njm zF9U0#^y?>{v$eoS@Uw4)`Wsgi?2mrqLLSMneZ52L^nKJ__37wPA<-h@q^7G@XrN3j zol41vE zJz}$>gTTG9|B}f8xl}zY`Z%yzg`_i&x&3LVo#dnx$b7LR4kjJWJL=G&VM(~0QOJ`! z7)v@BOuWTlL(M4yywfm}=$tGQAG&m(cYk0#DQ#Wg?;(XgLY%$8-L`Lp!vMv1p11%`!CD}cx> z`e2PQN0w%q^nCA(UYT*6S}j+%=h=U`vW#aFQ5L%UU?c_f1y{# zynNF@zR;cGCOYM5IuedCc{1xAr%k6bF&d#&{AxYbjt62SrD?H?9pP*-5o7|0!w8?H zP_m=o_rbyYHK4ahP5bc^pv3R_bj`V*^HC!~qu+@R1D*_#v5Ne>^N^`z&A|9H=c5Li zzhvZoF=Le!H%8(5@v ze47n8!OYCh?{dtmv_V|((+8!LJJ^HdWFEm-ZD%*!(;#XiVt;fLRi4E#iJ&asd%z=$ zb%MUyffFwLaRX`eX`OK^8ozFUrw0i-yRyKsySK+$z8G7!MHN?_)Xr=6d=X1;mIOm3 z)ZYc}dL$q;YVQINyCZm#s?)DdP&tmmINY3u&cfe<{9p2{j*I>| zlx#I*!>KmExq_!|nZKx;6q4nhO>`ezAEzu$iTCpgf*eVo`06O{YW!TeMyqX+Jnw;l zYx@LCzndyh?G+!IN1h@~xY6NE)dDRW;)HX=XPibMO?Z}+B5rHd$oUC)5s+frAVm4`&@`+wH>L)l3=nuu`Nzr z(l0(3QE1mz3F7j;Y{_OCYPzgHf0N+rTyhEf_K%sQcMNi0Al1UY6U!+BDqDUMyzB6) z1oufPtnI$m&PXt>=CmeO)~_k`_<$_XVCDcH!thFsep$J! zil$D>xY&G?q@EIsULdVjEEZ1W1laDbVg&RouXK=suJC6y;f!yT5>a>PW^uO-Q+Rm z_Yuc1>l;5w{{z%D5f9Qu&e^-{ChQi!1D!~K(R8e?&(+qi20zaOWF@9H$^2Lds{|n# zl>K2})G;lCwwB9q-egVhs2fo47k?=pTPk!)B-Pev{D8;Sn%^#~LOVdA=0Np*B{8E? z+pkfsWzYw{Ti%AYSH%;fa&hk@1Z?VNv^A?uRFwquejT*_X3Y7}!g^1Rqh44xVpjcg z4|8ogO%uGtFKUkE&YCe9xFL=3AnIIvE-1ynlZyCi+$UlN!zHY4QQ-Qa81 zVlm4P5tcQbHLoAXGquse>qTUh>P+&qIz=>NY}ox^mc?Rl+ncQaU75GwRTNHS@Ub*1 z&0^u8KQi{%X@m(fgzl6<@e+8-BIiE3p;p^#w!Nv%ZobQGsH9xD)U3qF+q;Q-9(iDk z*)OO4_DiSX=YjJ)!hR&xPCs|qi&I=swom!9RjulkIc!9!w75)8aRZDQJ?JJeBN=aU zJzLX~w5~i#Nm#!(Pe7;yhQ~_%K@aPvjZxT3Ogv4K;o=hJL1sfGd|jLuN?$=%a$Cw2i~Bt=`6+603?T0P1d7_m?p2PU?&6gC$%S zi?8VWTt?UD3Ao@HU<_y6H1{mMaF9s*my{L4tx6c5%4la{b2ZhxAz}enLST0Hg5-t zHu+PJP#Hjsakp0r_*zOF_@fvSknS{Yxk*PXfed5R4FSM!;eJdTJG*>V;CnEI&5F`=<7Eoi!nb zkA}LBo`)Mc`%hZoO#_G=~UZoFtsE(Avk%qZi!jf*?>?Y#K1wufS*i1B>7eV$JP0}sdToDHGp-(- zhZUq>!AW#$eX~7-F|DREMc|C0#LGzn8N&lPm`gq~<-`xsH*im0$#|>v!8jw?m@`C$ zIs5c#vEMITs3m^X_=fbhtrX*5EZTRWxk0BOq2HEi@xbF3f{Aw%<+9DoG9zB?MklgK z=@IqcL)H7Z$(Q$fv?jCC$)`^E6F`Di(9~Gzx{`uWxS;)55<^&fZ;#Ore2Fe7JS`)L zy_6KjDN{^aA#9WL$rOfiCu!!gmL+vYmRb?*+-1EBPjd?MP2d$9r1k8JnGKhl#_xwD_a$ji(5Tfd@ zt*utact2mSCiE!RUWQ#op?lZy35R%z4AOS`n2}IhO|9m4pVmLicRl$O%YBKPY1_B3 zDcn>JuN&fgqAz#y7unC$_=jyd9pAt4*%>CIYBjdr!#?|eq~C_nMxe&$*7-de>c_l4 z)X^y>G*SwrdKzZO4XOQy`ZY--TlijvI2fYHH_fS%L`)7_H^x&Xk zgDYH7%hto|*zHE5PE;@jmsGNv9sK>SX|8Q|QGi}RUdN>O)Un)Og>|B9StvcNmrktXW$qYXic<1URKj*Q?$J>H?Rly7)C_8WI&Y;s+v` z?r#<@@YX1y0jkpX&Z|2P09u+X({CWpD+t*T%Z*eNkrJdP#HMmCvJhq*h5vI#nbtqr zN`Dj-iMq1MHgIAe%k*A~)n-!XF#eRi050Z9XczGymYaCji+xz#Mwm5NsIuq4OD#R< za=vQC7)*?AlQc}E779a~JM-TgP37^6A2-Z&)!t12Y7m!nB9SxBEYZT&>{X(S57Nz_ zwpwSeD!NZsB30oJ>`r0C{~pz*wP4)3zSwv`KhWag8JXc)`%1?Vo?h-oeLg?v} zs!m6RbwWwmgPyBuu)TPmo+DP#+if|T18ROCrGs@+VxP&19jx|ZowbGuK^Jbs=l^iw z`hO%K4M&s#4j2dojjt2}n{~8$qGM2)aRoCNnx+NAdc|JMQm)%u_fvu0UP=RM_Q>Ld6gTb!T$GUfF>&^1;b*Q6$;hYbmsUw)L&zSt` zq8Zvg%3v(v5#Mc=x!uRtlcqH18HON>E>1LWp&%z%npp}u`|KHB$TiWUBm^7&9_n8& z)$1hVp<0W)Mts0z=dc@sfY%W(7|K&Sbn4ot*7Z^S7Li`KwMD5KrN7v;INGkDJ#?c6 zT09^;|Emdo5GRun0AqfJoy$ZO1esUUf15~UR*Lq?QdUl@3hw!6%o@e7y)WZS4WeE+ zum~z%>%zX|TMl9fL&p5PHo)$IK%CnXO_uwh*B&eHO_T!vGt2cG~hWL|EBS1Nm1;f@{(@mBY5?#UCA(FlF0+utWE76Sn@l_#dW&wDLo2|}hXZOx>DSkLE?-$3gk%FWs)fv1iI6Z5pqp%R0mFG~(mC@ZgVd+PE1 z2;YFS8x_4kV*D5Jo_6TlRtU_E93DQid~0lcfd^PeyLvLg{@{3@Paam7W#E64x-$PBHdQg zmF`Dbz{>tPZ9$$1jdeQ2#p3OsJ!ch;&q{R8h6IW&IPxYo^@{=f!7y~q>E3)+$F>3W zcylDUlp5K>?>g;IU8yTipWUuJC^~BGy8jQ@Mz+#2FnQEWnO*D?Y8Wlo$VCV3#QW$&B1WoyPpL!~H z7NB$7{A3=yk2@J$j*%G&hYr@9T0#w|RR}lgz^40fI2JG-e9LTn^`gQI|88Eb>oo9$ zXLHjhxSS&(tC1uXOqQ!1>YWJO_kViUTEnV(c&d0tyy;WBvfX+Y&rbJ@VxYZ`%i1+u z0S>4YwipLe+(5G*12|#H-2O~sD6;&OKy zuqbRqczwvoe;5uqNHI4Uj7TE1`i-PjW34~6A2;JtF)j3Vxo>i~s9W~I0jDCe_m85ko zWSr@}+@FsG@V3md{|rJ@&RitU`wR(Mzd;`pSr#S|C~h<{M1`o<0t4wH1mk>Qxj#D< zIzTv~b&~d1Dbxa)*AxTsVG%oZ zJaQXeD%OtmKsv3?Ob*QKo*tyveehMl2U4Tl;)8>qUXe@d*t3pLPixW@X03(LcsTnI z-f<%PZ+HT!;fFVf@%pCU-(M1(wq66wJS@8lL1*C*qAGAAM@IszS|mykxF+#hSQIwC zkHSfruV60s%pD!>hkEgb78VvVm-5v@leFdvQ`0Zickm^%u?tdO-$bvP9Pvp%lC*j)% z3+uW_4O;kT%iI4S{%Z7I3@f%OOZaQaAdzzpSI<6FT2H|iCV781ksL-C$-NPglO5=J z$UNBm=sV&q$Y|2tr?v7oz;D7iF;fCnR^@X0)3da+<0P>le(03#fv}n+{4PO1#)DPY zZ=dtrSFad_(WX*P@^2=^NGy`?Yv@3`wd11|EPFPQF^8neRp zk5~N`W`tXEi)Lib{?#1J$C|G9o$j9p{mg11nALQxe#Ka4wbUEl!~q}T!yt+qM-xu# z@^VO)9smcq6m~{_d6W(D6w_Kb4LpY*inRy;+1Cu%4DgP_GPNd1WVW3UMQRXpZ%A=K z+Z{om%-I0E0pLqr49M4tl_C3!%5DvAL0g%Ngygn7^GG!1;y1l<1f$&ajq*KDo-&F> zIqi>6Y^dokEwWF*A3BK#Winl~_z`$RE_=v=#K0(#3A(<%e$8Y}eq zOD)jimb%5smZ_g%>D^+bweWJ!ktORrWg~s$ALW1Ht|rIfHus0S;fYVzv3*)p2Myp! z4qvf1WLX6jQ~21#T<{Y(Xoo4EnQp+RZj5KMf+hDF>VwTG?}EMvSuzqPQ!FjrDs*$( z&T%h0Uu|Z`nK+m)TgmH*7BlI0gVVpWF{kf|2+ojsI+WSsF^o-PZ=n#w-|Caolw+h^ zHp*E%8t{iw_0=@YK)zFPLug>m2&B0p$QDD)*ks%7ED5;KlG{Cd9QC0gJnSG>`?wMZ zfysSXPym_`H}|bQfBDcTtIy+Fn~3K9TUmV#3ZZ}w{#|8ovuo)BqH#mmyTe;?h5$cG zJ<^PI8Vu;9+>(ct4dTgY;8(4+Cj{&5i;zw5bdAj3h^d_O(?1~ZPrlTQlG2gme4f&} z051&mlzG2wAb77eC=qmuBe|P~#z39>oyuO_bAajoW8VCV`ohyvx4UG%tUu)I#&EON zTh*fQ9r1)~L`>+z(lO2QA4AHw+*F(Im|b!@DJ&(m6;iehhm&*pJxEFc*qe|JemC=9 zM4Cs}v(ASuV%ut!Xqm3qqRXqmt7kR6OV99^5pH6LVA-Sy9`If&E$+%!LDtnjsNG<9 z7QS1w_V2u2{VUQzG#>u`%|-Zc}v=4xAbr z(LnBj{F^3O=2p}B?4cD{efKH7&7IoiiLA@#zKTNG$Q}*u2R<(1pQ@&Y3BcbHDZu$f(t1yf)d> z%nfLvwct)2f6Z$cuUZHt!wZy z^Jd0SHZlaa3ERphk>_zT65|tra95(&z^Zo_-iLZJFSR&Pm$fGuXi+GZwlK6f94RVo zw3p96VZcih-)+=X$)NUcI@fs&_gM_3ajcn)#zl#c#+^VN&Ob*kcU^2lwmcYD5Ig$= zII=(g)MWg_xnw}XvSkN!2Vru$!4NwQB7_!dm`iVjtiH#he}vr}E@Nett`g!~|Tu zFNxZ~CeIHNziNopoeO~LTI0RXSRc%{_Cz2h=sP%sPh+G_vEEY$`K&p!0}m}=uRD4= zIp!7de)0=DueE*!%6;m~ZF$V0GO=^uUGCKhPFp6dmq%`9eZRImuL_TI3e7tzx6e|n zv#_G=dv>Xr_a(;!T^On6kY63PvMSQCsY1YHoS#1xzOxBHJ6XT&3lFM#{_9l7eXfQR zIh@e9P5U{t$ZGLN1x~|`ccT`8J*1m&j3-3C}5%^=#E-t$N;h zbhnjzT;ZxUr^T!>{K}iiRg1XxSMz=xy$GQ5)H>oB@`6YiUfzG=sp7NSO+rZ=a@b_4 z^Tv-Q^{^gve>g^e!;gFG0V#rM;13CZe$?&^s=M^xa;vgU6?1M@OT*u#;}sF+W=oRN zj}XjsqGy-31I4f9sZ3M=06xRnass7k45_c{^`3cItOJ+!d}9v}STkgQg}Fkxo!>2Q zB>r@vK}kbrG$iQ7NVH=?zmPlVf^^&QPEQgHpw%&^k(_;A^?Q_wm6e}IwF z*kcTsYv0y1t0g=592f!LB4^;u?T5{S$DWu6&zj;y&EZfcxddO1>$nejqd32B6ZU+8 zdx%UJu~F;%8p5Uz0Z`0$oE*Q6#he3CBxqJO2P~893H$46!!-6fJ+j@8!OvLD^t#?tz~0f_ZY1~DW>oCQqr|Ib*#=-syfaJCMcf^$%Gq1{`K(x`y5ROS;WWF$?J^? zrRj|DlMK;IzE?0ghvqf~5deOR56fxHx4x;*RPEat46S?cvpuBgIP_!Xc%nxDCU$>c zE2<>Uq)!{j!&cFSbL-CXAN&xYi=E$oa7p}+#0jME)yM!Nxg~hKgJtx+%e~@dtCe+c z;YK0r!~bzO$vH8sTVMLwewY1QeZKu8E2Z`Hz{$1iHHRIeyd`0(#k~DeP=1ySxdZ}o z0T}p2OTbylsFFl)IYy(?yI;m!5y3}QT)z)+j@0deEDCHNCvLKJbo!qSi);PN3In}- zqVUY}5t?LVzCg@8a9YhP#y?a?yh@90I7p|d=ySo+iu1n#@fcF*t>tSbSuNIw7b$SD z$r|{?yt2GM702^)9_BkPN5BoB=9%e}Pie(I59;p9e|6`>td`(2)9FLa)7bBzV5c}YIS*Q532i)^q^tBd^e|9W4B1&VGKx=mX&I7H{^uz-^vU%LFDXJhG7<~g0y z-_6q6DZhNJG8K$=jsGqA5Q|MnePMzNBBS{>;yw_~z6}?lrk|$@`4mxfN*si#MHHDrn9n_7VV6TX-Tl zXnQA#u_dK6XWDzj_H=zD*J?L!(+(sh(!2RZhoz_$5r-?{mFjMre4hPIc2}#|B)k7E zX4Rn9!0AqaUTE1mkoMlvAbQ4nzmT02Fa&fez1vVrQ2<-v-ziZ{cE9C6%+o3T!WH#T z)(|q0DSZ1?xo;tC^s<+eyo~e$a(Df~H2LDIVtUD|WhZ*qbqf4*m3GFz`!%iwiaoc( z6u-mbLdj%R zi&dx59rs(+{BZ7<`?J}XI@YXpq7F<7pa|Ie7BIOd?Z}Gu5j`fKN8)`q5jI`}hP-=E zv75U>1CyGe$G-;70g>^LHd;Id2CzC%YX!kt$XWveZVrjC$++ zTU@sy$qfyNdEQYTIpXiN#4^-Z7n4-kKBT@r?(_50vzH0L0A4)Hg_}j9$NJ>Vc(JqJ zh5oVu^_kFf$@sTfP+>$4b#2psgBFM$E)@Rc@RJcAGe-6~+wCv3?%hwFwBVfv{hs)# z?IX__xA{SYR%w>RjFS>Fy*^-FzkxLBl$u8G*K_HGQ!}mG^k^45wZ^D5-a(RHELsZi z>Y0U&>)$&2Gfz~GtwOj$yu%9bKUI~wqWL=e*=B53OB?FduNL<2lzyO#&)@D?c5l>z zK9<`&fCi1A-(I-CFg|wL%JGlAU{nlhy)!9dRYN^JuFl0}YApIx<9uVkwgukZers`r$9i=y6|%rhU$5;kmWHXC2zV%CYb|DW<&l zT@7Tr8K<1S8 zMz_zoN>30|A)gOIHO#&%tr{VIewyGgg0=ry0?qgYfUcr%HB0z!EDqCz`QCX(53}ruGt4|QBTo$s2U2Wtnrv+|a zPXh924DV^h#2NkXSRUy#B#GilscGJ9o<7W-N4laYLPE$HT7y=;e}0ad<$BT<{DItv zw8wx|i)T|&eIzi_ox>0!N7?E5uai(AoTq-KGXI62olGAVl4s*HRL3VvQIK0DPo)jC zL4CHi<6y!hn3<0l}Ma>vCCHw3*8vXfJwm>RT(2CPU9+jyj!{u3;&I=gD)k(-xsAf zv<9Geyl=nRCW_aDt=i-;K##VfZLmuXT8u*~VyETBV4m=JE}Q5?M(h));0`W+Zqb@$ zl(Ljxp(e>IRQaQ=R8P?xW1L~_0{J06qyfK&oT`#@Y9oAIO@Z5${oj!wWky{)ysLr{ z)vyD1c%US0!?NlICgDK8Gh;B#ygHw2{=wD3S(1(t6@&DhMy|Y^?gD`7i)ddaVFh{^ zmjdu%@N@7F*=|mIa#rPlS4Dd7D>$*60qJiTd9^!K)y8Dq_a|)b!MJo?@vo zkjj>Z0ov~`80QT*ah)~Z1Nja(!gY@yMz3&Dciyx+4o6Jn3*_zJsj{%1&d)k`aw@$~ z^4Ahxeihsj2-JyfSpNTb`to=v-!E)SQb|(@iA*J_EK|vDW|X8-6j`#&6orTp*_W|o zDIqDclVmscoe|lx3|YoL_Q4o~G0Z;ie1GrfegB_lKIe0u=f3ZAuKPOIb+PXNULF?Af`wl^5ov{_}Z}0b`%G*NTv72;q+5$XcB+kn6iX zfc}`Tx+PV=|4#{AxcGIV>yp!u%yp?QQ+}~kl_1_6!4Bq7Ynszw!20W}Q0@({UwO)+ zPA%W(I3?A$_}0oFCPDOsSVT~V79@Bp=N7PJwZyT5I`2*9d71Z%Rxs2UK=vfBF!K+dv0W|K^{siFQ>AIUAOw*fQG`zUN zx(zwhjsEY|BQtn_mojPlHN;W6`aJni{lK$<5|?GhLnVvMCz&s;|Mld}O}CSN4t!@3=p5%}P(o8fH& z*lu+MzP0jF+Bs1P?5IVtD%!2cTZwDoDp@OijreMqafP;_ae6vI>%WgTn7|7q0s?t=UA0zVHbKR<7s6C!d55>wh(+5>Rm^21tPM>jwnzoBXn|oV=RMvdZ(jQ~7&Aua~}?{kx27rYf}XlQ!qs zq2Coa7~^HlTcQUGQZH$ALzojimSAJ;0snRK0P-zf#xJh8$1TB4Q20V+_wT2n^1Tk; zmo6VKY7Ozc&rYYVDZ5OnWZF`4UaXU2hI{7j^hjQ++j=8%^u?^Bf=_Z%%QqNnZmrEH z_q#=63hkToy@bTOm%$FQQqf-nR-A?kKm(41V;@qDR9S_N!(l|OBUnHGJzUgLKa7r& z_SEMG$Q3VUhzDmVay+nSbTsr-dxyh`q>M#TKP9qU(6JM46B}WJ^o3+#+LvFLpphuk zVA7XcdDKHjjpM7GnsAn5U9Va}THE=Fx>dnwu-YBBLK<8%ykK|X^RQ`dHD;pL(h9x9 zZGut&K?gtRJz#Uu{eZk34+?M>#YmF%y3SpZgxv!{D~eNl$)NF?q&4K5U0TeZI=u@rH$Q*90 zx<>s_VpYAW)yKG^Bm3K4Kx;qUI@lynpeSVO*NfNRp}SEYdTPyD(gRt0$#{xzju&Ym6(D^-|V05gjMd(y?)@f|RGXHh1t!&w4M?DXEM)Lb> z$!LetUmsF4gQ9Efvm+DnZC5_c%q6=lHJdMRGMl|)W?XQa4H?x_!@6aUn~q<5a(u+` z;+5m4!a5B^VlLSp4Hx@4Qh4~{0g>Ce;(>M|QEig&;}n62%T;OTgs-~aAGs{d7e1F8 z?!)hQEbH(>s@W;0Ik%cca{8ij#&tZ$cWjc8){Nn0aAZAVw*gAb^hVhxUhEk`=?^cR00L9Z|3n%wG5?VJU>O8npdG&L!Du=uDY zsWQsbaDDZq>6e!9?`C;>_ot$z4q-pQ8O1AvWJ~Al%qrWRF)0{!BCf{IRL14Xx&5Z= zBkl+Nto%u$mksT$t&dLgDR*^)Ifk+v>KKU)KeE*T%WM^&Kg={o7P7zMp@s181 z{=N+k+JZjLf_SH(NQJy75=Nw%0|m$y9OvFwu@)JJFE1KqtJB`nG}@!R6?NZgQ6FS% z&1k{=)B;N{ym{mUbNnT!_u^x-0);9OY+34BsOTlF6K`CUdirI73(65)&zeWj6Q|Gy z9M8Zlxoa3^mZT^J?zSGU@5sW&BX)JUVwTYDfc0IG4o zcKo&t07XF&^D>-%7fnBlQQu%L6=5SO*h;Rv4JrT=!qLU-)d(M)jjnF`VG>*f!P|kC zfF;9$2LBxCERR5*L6HFW3#pa}GR}C>q$A+cXDuT?0W2gpNOj)(SP*L1VOqFu9Y1kO zT$s$@9q`1Qv7WE>qOP{Q0x#>@6Fn+Oem+K%8bad)5{SmdC zCFDk?pkpa1lT@IByE0p?Bcw%|33}}n6#pK(+g)MKfzzLL?SreaQ(RA`tpn^hy1q4% zhzw+c6EH#S0q>LS1kwe{mQV$HC+`vRNv+XE;Akyo07VC%)TG*{AI$&A)Q41=vyS`t zntzG)Wv+WIAW-W9Rw|kO6z(13f{BGZb5inkwu-}#$Mo_U%HBhNyeW^D4XN3iAx|MN zGHH1&tVgU}KO5l6;jIlg7QcNU=(l$>2Q`{R!=-`B&IIX_OG<6K0?;5|ScKM{@W`S4 zFnd_{zb~k+J390P>_7NGZeXyIH~7P$rO35`;rup{auLpnhde3G+LBh}WSaZRF0T@Y zaIeTSvYI5s{)c&%PP+Qhp~RV%jpZc*vebGr|N49g)K4qdoM`D1oZ;h}C}oylC}(G_ z)_3c>+as4BpS45wM8e%(&qL};wsGv{&qYWQ1?ubi#8-au(v3Mb8$(;QX}Vl)$ee_~ z2s*~Q?}F`fSUGu3Um?)PK)o`)QW5eHq8W;1)0>hq~uA_0+77L-$>r0ZSp0o%C~qPNZ&q3e!Y>M8pI~5(C z2pT(#!kCG#sn#Jnc%Z@L2AQjb-;lDTrCwgh=cMVqmxOv0`!-JW+Yz+AX3Q()yPi}1 z1N;TdWF#xnhfOg;PNgCVHXcoTa0svTHi53qg^QawY=0!vq!zco!kxQb7oQSWdD72b z*z=xPUmOSV`!ToN>hl73j(g1aNAt0Dsc}Z1j=6H#hr{)Q_63OIFT1WlkkW01>e!hl zg(*;lm>QHnLF;OL4r`ccy8CJG!HGlvGV9~^&;CE)^|2|V4^s?Q+;C@hpj zXp;3?26_Jpdj-`v6Vw*>EEv11zro!h%V-#O9{06zWNN24W*p5{D>RaJ0qpbC6o9e> zF5yYRi6eW>K{%8@SS;{jtzipQU=oK^w~eTB+KZ|jqvbdEa<08mU8x=kSHK;bW%ul7 zWzY%VsT_ zlK|OILg3)I5un=1pln7D{mWLk0}-^}bU>dl&pLstJ;#hvTHiydbtmN$9nhx{k8mp_ zso3KHmX#?-{L)RS!hb|Nd24|`dywyYCqpCmI;AX>f{!yiRvTvL&0|=NkkrHwcfVhY zsRK?*pz05whukMTkTLYl)1ShHephxB`7z#*c5eTj@MCT$d#SYH&h0=Fw*R1DOsjo< zT5W3voCqM|T0YTkC1F4rpTFbXJ8d$gJ1jFe<0XxnHvhVmUti}tWubW5FRUg~JBLbO z1_3Ti@G(1gGTHvI1O%tBu6dSwl38W)AWPbiG&w{qj@^Z*g_6Sr(m}HUTw(xOVAE_l zC=$9!;>4jyR5Z&2&iT2s9l0Zu^A|cvZ)D@Ah$gsNHn@k*M@L_0T%mf<=wxO1xFC)a zfE?m7&WEiq%pnZiuN@S!C3@@gsQ>Mik~@c70%L}!nSXK1(A!a^qB!nm2;KpH?BBzB zcl|b7pq1FHE^09yT1&(1mYo72K`Amu8%M>O3rAJ+(1)dG<9=fR3f2; zz=B(F%PPQ$WrxF->WCpi+y4;MfaRPAwMX3-1D9?;!PU;j=VlVD;y^(x&GG zt1a7sbv;O!qL+Us@*C=!rEq$sD<(ZGa2|j@;ALafJfb$BEE?se*zEPQv?YG>b#L zN6$NO|9An@FcZ`#2d`@uV_+H~)(w}mzy_L>L4r2&j z(`&8(Zzk-M=*?1?3z4|Wx&3T3NA@%Aqdz5CgMN;_d9Rl&AsGZns`RtJAWe__yl_i+ zpmOvQBkV?o;7$N^-pm;T&Is+W3CzbHg_vi?Ke1f!uZ8dCp(&48a(`f0JmUbf4hm<> zqcU1S)DjYBg$^7=o(Bvg3XS&Y0;mS~CTlC;nODCybu2vz2GlVaz7gNS*sIA!Dbs#r zP+O|BY8)Q`tI-gj&!lDE#!I_LroQ&5)d76KoagtIV0$%vtqMmb%l)UE4KNN38uI3T zU9Ziz|CrZd1i4lWo>0P&kA6kqH{b)c#amB;*`v_yOsdx6YUxFC&1RZu=HBkZ#19Hh zxSHLwK_}A1VN@}%H$z|N4p6tKh)QUnDsKycXRj8y?7|s$#wel}J=qC5b}c*dvCtvf zYYHiobr!iu+_NKy&%-G>SKUq7Jy;}XXLp?KhYP4-KWXK*nMxCIdM|eyw83)Nx|F;) zYfAJE)C`%<=Z|GOJs}fbacs8f>=>Tg#RPGN?KSo#K|(;S_Xn;;4^8_e>DA znRK-oSEfukQa2C_$HI|Db0Fj%bsxyF4TK}}*)ujRMVSTBoQ5r=Int(|Gr{Q?BlMKw z&=>+1D*M-*(4Yh#b=G1Oam2|CC@0l4ECni65uJo}VAsB7t4XAhh$klnI_^zN=+?Kf)O=bbfNEWn~nxMdDe{9|OD&)Y&HUZ zI&(hAaPCz zbO_6D%}lq`cl2Ld*3H1=RPU;|DzWFyw_T*&E_e&{p#LO9rXG@#d^Mg-QrD}`Tlt;6 zocz@bM0|E9R&@3sWLv)~IA#KKmD$!uzDueQY_}BKzx^r6{>rIb; z8_PkM2ZV5YAx+X2@P*MMHGL_lk7vL1d~>N_ezWpqL+2ni*ox8Nd*n@j9kJ)zrjiGZ#ve9zNUx)OIrNezOPSUr14s!aW=??A6a?d+)0-_Fl* zq>F;K)RQq*b1v+X=J}-{x#GtC##b{Q@cr1=U`9s!<# z6M_xe`xDZ>7RpYqo;vCKi+B(Jb}XgwKO+OMFSlDO6l@lyn8E45JEIZEZGW#$xf_uj z&8ZLQh)Ilv;=*A}i;_bQoG_dj;K7jK5NewXkb4WBf#;2a9T*LeP`NG7Og|2O1aX;@ zN;gJM1lWN<|Hi5A53^h5OnbSgIYT!Y#9lZcWd5A;2Kur0QUR>d)y1~AGLx*cs{Gmo zFzy+W+@Lvf6<_S%(G_~fZd9E0)gW0z(RyKTq*vmGovZjiWsY>G%u!rh;_Ele0`Zk_ zjtr^fiLZJ`M_YjIk4$@0^M_{*!(3dHeB*o-jaXdjZ&ToowWP?bo{Gi6XT2%^v<}*5 ztNJac-7IooJUNP-*|p4NC@Y4rq(js3hqG6tIkN=J-kNb9j}c3jab9A6WLaja{%i7Z zo2)D5`H=IiU0b*L4G)A-^PYAdZ>^!oqEYib5L3DJQgdHz9__APW{A}80z)Z!9x=URNLZYvI>-LP*zIp&+>fh@1@e5I^LGCG?WLKzGkB-sSO1^;mRTx`*-u`m ze%hG@5q|m+{Z`&wz}$McE@@Fe+4zv?VgSCTmE^JG%Z)(d&5Pjjg=SuT+B;l^AI-NB zdO|5xkz3CR-^tncqrUm(jm({>Z2|Xp950}0?}-m-Y67sSQ*hSKzAL9Ylb)!CW0Y|$ znU9UK*e;=1$`kDn61#&E&i#B;0+MMb$pQo7=nj1E0J}<~tYx~Y4H2W8uW>@LfjKK; zTxdnDc6b7Hz1a0vXeZrPQ{kfVFYWweR z{j9%ZQawSMW}kW+iH-U5w-%qFCskFFtrY0qb)-cwOFE55#R-MD<~fzoTJcZY81u(4 z_Vk_{`>4pBL+z+P$;!Hf9z%2hCjamn*i+50z zk~l#4WEpgO`^X`klD~GCN65d=qda?z<^vciD0H$m^fTz?K3(!JXlc!+4gq<~sHPBE zx-@rl1o8u;G0>K}5RwOFh~0avfav&P4oAn^0r{rN>*v!iEh{c8YA}RZ8a~N(Re+8SDdrG9u%j&yg^7*wQj02jhXGKTmP+g zNu#bItL)R#F3NeOq;tjKDEtJ^$|28|-l3dY?8;LXeM1@a{iM5ewa79erx1LV|CUS< zFZEH0)%)a}t(D(BfYq)0ud(yPc{S_)K`M)Yn#5}77C=rKK0y~;-7q1EJOVZtI2SSg ze0}Ba%DDW=xy!1c@xmd(zdvr~?c^e(uLu_1_$KPmcOrzvD|{qSOT2S zJhXR2Uo~8|U7j2dIk&EN$g26Lcr5-zB02QRdAvksAs|Td2_nSiQl{W!uy*Rrl)?G< zO{$Pfrr>USW~ZxJ?7>HrMI`0G0&hbS54^bit)jnUZ?-lo-NjZFwU9@n ztCY)mEnF{*#r9uf5cg26zaIJt(cc2d%QZS~A8+MrOBs{G4 zj#2xi*UJMSFUCU%-$m!&B;q%t|FR@Ba&Wq3OX5w~+8V-oKt@!maYZ0{v-#%ah&Jf! zwu6cDO1^g8Q!KHc2w?F;bA$TX4Uh`CLEx1BuYDORy*2+Pz}x~ z5)$py^orRS3_I%pp_yVuP?0TPR?RmnFIMDVf2i>1q`!^9Z%@**3@?#dIZqafrcL-v zss*eg9iE!~+;VCy;vB98l$~*VRudEHs3LVW-;>~%afRtk1FahxvHj9NMya(!raBL~ z{1H6%K!ejO_}G|y4Ytzmzo4F}XA-i;6%>$K9L=ze+0Bvomp(Ri06g(svLstX=sm~) zWCeckSPMNE`{GEJc>J>WLJHev?W6op*I`Fl z8qO58phBacg5@bcE~lMf)VzwEmv1R%H}@cX$;q{R@KoUX;U@Os3nW(COw+X$d?jw6&r9h4kMn545 zMx|Q|F6li*D7nX~ik?PiHayOy0QA4uhL^vC>I`Cuc1Cw^&8_Pd;59~K89_m32*_}U zIHjkq0}q{Gp6-*>7>LS8tN_X^QYHy;HvMnAxvmRMbaB+;{esay!aC_qRnOv(MzuU! zT|M2dL$fw4I$|-z=Qmu%&D@A}_9AmE)ABt4XqjV)K%WF`#5Df20*@L0Fv?2(YopQeBh0GXnpf#EpOtHsmK?7fZSws$pHc+P*g|y( z2Asc($FvCC#oKyKbh?!#Ok>{q1-+V^hyj3$qE5lA|5~OBKCk4}&=2cwN2THoViqhP zJ<^ks^ibUGn>LWCTh0V$5Z`J#V7&$-&uBFHcc#W95f`86a4(kEV4z&msD_@Ci+{U1_Lt{moa5nAU(ID@vxKT+!6H7sz>3H}3s+Qg z<tnr#ffv=m-mh_r_U2_H(|GL z%Pw?*eD7<%*;z@O0AYqTPSlmsRpzu}C3enBwxN5d&2vx5$gQUJmyX1rbDOlZK)Dj;Pb<9+YmVG_o!uAvhNw92%68{ z%gF`+wTc%1$#oWIqy zXs|Pg9SU=dom;|a&$$S+ZSKBpE;qFEe+y3=9q4O>{Lq*88|(yHD>`H}RTmI+-$AR( zl8^@)EYADXbm&#+H#vJDR;;C~b|p`fN9>ssWyd#FGODp4#VSdW7V8T!2LZr<(f7Ar zVg7N(&yI``#0(Qc&9f*w3i+Pt$F2R>ZXxljP-}!Mny8H1ThRjU!J^@VB0enPW$|5V zMgnrfd^X585XUKwqWv(j07+1MZ(Z$))`xb?>b{P(rVDE{7mrvEZQiCjoeHjig^ZhI z*>PVDsQ>vrwjd>8BaitQX&(p-(@vkt^~r*l-zG%eH#=qW(xhvoZQSAsujUm0*(jZ z_XXds7M%!^csX{}KGrpZ$<vDzl@S_OQThMwdT zMs|Px5xDqv+vMxi5Z-)idy+juk;540sQMrHy9iI+jS3aRJO#CC$e)Qx62tTo|Bzsi z%nRWFo)FPtaf1W$KfesUc^Ina_6<`b!gU%~xv9T9=aV5m%@2|3B)Ru0-$IRLWP)Ec z^9^Nf^V~2dxwOAJoT@!n)oH*al*N&lOuuplxv_}kxSuHlB|8`4K zB*=LdsH39XZ3CoIy)!f%N1UkTSF*@iP~~?E%=yQSRDAL(@^b80C4)b*MRhB(aA$4gdz}-cCNGf@jImjT*w3N5BX6C5DcUh0{vd?j#F4K1L^rR~w z%)f1cDfccbpC?z82!BcLO={T{qqFV{4fZ9M=z zaPYd4VmX5W8o>BI(bxTO&vw8mQ00n-c(skWWS^(Y^p?=bPDq!#czy-yZqvAV&;oq> zIjj^j)56?~!R;4uKbqb#=j9w9J`_5ec{yW2B!;dJMfmK3Z{Xp7_ew0kbt)Htsp%$hvxlV8 z5|G{AS6G+r{3eSG%FSwQdL@2i^)`=g0`B6}&<6A78539Uih=39VF7jv9@|2>8cRwB zEe~|Emh*#85XemC9Hk1jxxRR?Dekof8+>^ zL5wKa&!^&Q=CjxbPPe5L-K*b%)XJ+vdrB@|{ZWKl^Syaj%OU#>_lDd-l|YoaoI=b= ztWE$1gbNftd-CjE$g)!t#@;b_&GhI`S4mwL|Cqg71&qKDev$-WvC;M5FaVfOyw z@R~|LJW+E=1+=Sg#pQ@}+8v364z4Y?3ZC2B+(u0IAm0MU7X9BMXB>hksW?s@!;*D5 zqm_WT4E>l#6^lW!r(8LAn+1?x#aqSulg@(-JWL4Uk>c{r0-t+@UQOh)p3lKk*4u$0 z=cQFN%sIp>uYN#8jRFV)pE4MV2lK+`QiR?DF4ha!9&^XPhkG{ddSq-Z7wVNKzPcb@ zu{AMXG*eFEYj8n6Z{5p9UXB59KHq!rtOe4Zx8Dc!qSQcTck}Dpyob4tVV=P&xpxhl zS^7V~{Icf5Ke+=@lZJPJ3Uj$4Qq`r($X#~>?*a3YZA+r=521OKz53o9YBoD4%Cd!y zz_dGBYAr1R_DqAbqydrg1kGl742Z{|ISRVC_w&k#Q&EWf_*Y`{_ zB@(?G5VS3E{hqf`UH3n+wpmdZA-*Dw+y||5{h}_ZQN;ndCcnhEc1rCYqUV%HVAE9% z-aCl2(?447-0cgsI&4j$AnvrTnOy#%F0dR8K&t9jT%XtCiLoq*H^Cptwnc5D0!=RQ zmEVo!%;w?z;x|{GH;5baM&Nk5K6uSUh`(^{+Npn!)|pc79!-eOU*0CIP=(GJ7w0-; z7-1>mLG-@6M|WmK-V6i5YzAIrxM2;`lF0V)c&1g+0B<^T!yy(qlOKuR8nWqWdcrWHF0r4`cmfuqslUj z!O>vT>oC0|sJ-110rwLo&}rQZ(*?Ck=M7M^cahb*|Mms)Xn*HCY(|X5Ulwc9E3aZ` zSBf>O3jox6VhZ7Nh2J%68`|=EGj4tQxm4O&0UmWmvy4uuiQezX?Nih{v#&krdyZI1 z4sCYsf0O%YcGsfcKAI=rpjd{tJTWcRcB#6v$Ya2JL4l*;95sE!%KYNIr)!mwb_D1R zA@6CckEw7>pKWGa;n~#A+_(4EMT2xcKUDH{VKMJ~Lbu$M-cqDk{1Yk2RqBo!`8#x$ zW16Q*N!al{W%*lF?VYRj)@v_F{Fmy1uu9laH7mPrj~VY2@;l$CIocdEbfaF^{MBln zL&R9$QF_&D&FY3fnkQ${1%+$y4>@1RQX}O6*`hKs*TDH65Qe|2?kje`?rebbA0JV3 zTvYmjbKz(BjVBd;4B8-h57F!TnKu6O=(77$4~_ICEL8HjAz6503KQ9lhW+$auZo|> zhf6;ecr1n*)q2=Xjyal3TjL*FPXRwVVe91YtY=A3C1}JDa!TxRpwo7ijqk=39Ou+*Vc_IbASy0gy^ze;(b)I% z+snk2;Lk{Xbl-@+1GV)W_SGW}HD>MlM@V91m?6md&fBW-ULb&n2vzY+Nl^R_8$!$< z@Ea{PAV@eEamIq`Vw_0~IOPX<_ChI4eQH+ZmiO1D9MqCXX*pK;$?bp2bD>x+niw zOl8b}i)5cRiuutj`)_$pL6UM#Q)GL@A;(DV&&u4M)hFU^1bi?UTIu zj1FM=FC0zq&E@-b)eg7uU&%J+;-^!Rl6QIH1_pos@2pKL;a`* z`pGUmf>Q5g3z(mK@_b+RgXb?|+@Bm-)e2(#<6p_S&WjRrHXlmrLQuaFjTw`pLTv`^7gi$m^x%mjT-L{Ep8=L{-fX}u z$Z-ZLQ4OlAl9V|$^P8j{lMmT_aq!F{j^WT?p!6fG@#e1}3rt`@u|n1rt{m&ec4bw{ znunFnaKW?ueAIQkSHws2%%S{)H`a=m=;&TQ1T zT4R3Ao_8bR8Q6~6?PXa_qYpodBA6pQut$vt2WV|BXKAewMF-0R+u^6r`zpAaAqjUc z>F`t+etvfK*sjYF@O_1+BUts* zcbp6SH`bMEJ&BQsxN+=QI;OT`JQBK~|Kn(5i8HvI71^-2OF4sd;)i?@XDGFDhXb2a zbvsmIv z1H6tPs36O{d{&J`MC25>F#q09m$PtvblaPHNq*gVmv$Ld$~fU{!s~YJYi;O5>b(W9X@`dFshhTc>pPhl@QqGn*(GuImj{KY>o;Bd z(E~u^{eaDpgV^S|{Ddfy_Ed&^;CjMM>Blx!Scge>vac?(<_N=vV*Z}_%YlWZ>ofk> zIu1cFwS~?5qP1`{P{%%gdA=k+=`yIV>3mm)cXR{E0yfRhB^!zxe%P`!CpJZ$aM7%1 z+e`*L*dA~RlJ3)4-=uAKrx8USlDmrb-;bNy#yZ4(warrXR}cK)mmCSNdp>?A_-7*1 zPAx;Hjih;=xpFD8*R=ZilrB=;Kjbx?TE+5-IdYQIV_LTEIYjO2Nsx(L4 z3WG@wd%8C3WKee+)*S@C}EI5jGTfUoD#qqx(rWMfE^AdG$ib6ULu6k1THx_Yc-Yd|m zhuTkR0;T+o( zF~s9sl70Xmy5>t8fNV4F>v?pK=|l^Q%v+9qj!9y^!?3R$=F;RS7gGdr;QGPJC2Yjg ziCw{WI-|S(-O1d#F%+H_I2JaszkcOP3z3A!1+?-s&6yCsyU*n3234&G;T*_wW>qB4 z2a5BiU~OOk8tiyG|iyhw(QB z2cPSy{3#=);I+Zff`yQxA#iPHh-JQdb%}ntU&x7sQ#I@9hC92(&KdK-iuuB`QzwYN zlU+Mo*s{GgIINHV6GU644ovuFlqukysmy#A3(GUoWg1*mUc`yNr#e3bcT_%Q7F|@B znH`++&y2p_%x4pLw^-nUxf*yHz;UbRDXgQ}O8=ok^LvpaDJhXJ^Q8yL=>wzhiL!xw=ujxRXVlP)#l;4?)Kp{K&%&wDruiMs{F=wLJ)CXt%?d=?iP~9-oJb{Xm)K~lM z$ORI=`u3mF?;fSiKC~jr4GQn69{dVT8jMcBo<3XQ8iU1m@qbTAc!P8bpI|#ZdIak# zk?ZU(ZpUeBi@~L;t!JhkL{WWv&m%U3c0-Y}R|nsog?`&IM|`{psQP}LJXN%+kc`9* zwYn*?Of1I*k}>Vdl3}ko$-=^(lfA?%4I!vxmBnz-hMrVNjtonQ85)1v&8^;P(Ssa- z#S30hIXQMZy|x|tH_~fSd3{08h|_r#y?(po3N4WumPuVAI-~bfam+2NYp9Yq>iAvmm;nt?(lz1b=mMnbOIrKyMKOWA;oi2%JhKGZ^v|4)z!f%&{~v0fSac9fVda) zYCXUQQxXGV%l_o7o*U>`m$)BkWSDJs;m?CMoUl6;u;BPXUxDwUcObdu(8aGSvg4<$ z^>*ubKJi=nyEUiT?TyLbj^14vc7XODe1Z~noLPYTLl<`oH!JPY=02w9Rtl|F9B_{o zgOBIW`izv#t45~LY!nlSj-yJ-o(T;}lP46d)mg>*jbFA`-%DeQhgb4{EKlb-7*mKy zPHS|eq3?0cbf5fEtzcMkD;>vtT5WTGGW1je+kp9L(H3VUg5@+!%C<@FC8jq{z4 zYi-uI;Vd0`66Rr~IMj^!eef}ernPCk@F_baz|MKo>%s9xhfffd6(Ve5QpP!MTH{{! zr?f$xKwfE%KBG+g=TA z`qPGCuC^k!97CQQeYjj-H<|)lws{apeQrs1l!C13*6mIk6@4Lw90Zp>bJhIOzdW%3 zoqNy#j6dl;c4trU;!$^2%y8B-!XFZ=VC>I4f`2P&Wb!Tb=DCa%;{6i8u##qnh|Y?n z{-bW)F)!s5y)7u0O8Rpii#J$LVFH_)LQtdg((~qEeG!xEW*d1E(orK1id?x(D|1-G zDIEepxRS`u8_?~d*#Nr6YBl+-b_f>tI7rHfayEsM`WI)#t-idMBA=tX$*!ggjV_<2 zC%1l-x5&bnY#iv6t0^Fcg2i<5qW(!_@zg0Z~v#Q6ldYj9`A#%8EikO6|evN z+({(ALxd;0-6Io7YP`2RWpFmC`Eoi{b?yV=5k~Ifazl?G5XpP?cloET%{olwk1JUV z+O;XVj}{>!R~M1@Rt#NwWC1{8xIsJL`<5k1lNB=qYI!3A6YzWsq-_yM1R#C0Wn;Cq zFKC`}l0ZpbQ=t6LHT!U)f<@rnje@9C4~^Zh7tyJx&z68Rd3K0H_;x^kIehSnIGt^D-2W1a6o^(=hY<(&dt9T8Zb;W;so+>oogu(j{@jqz|BoAkZ9 zqsVPS`nb=G*7ne`9|+>Nv+r9L;8rq$EF)&;2Be-GCX9NwJe*n06g2POiu!i#WUtRm zaCQM?C}qeayz%A)Asceye&TgW-U%tS<|xJ&b#^OoyGZI`t>8b0&`W?o4+33~D-nZt zIXia;A=5@}=MKIX>HO?^gaaiNJbF}x`nFx4{2>Pbs zyFHtzpijq+u9*sEilDELIJA_O3l?o|hGG2XfH>DkWTaQkZSs&==6B_i(|3uL4@SPO zO7t`bT*|AQ)PCxYgDLCY0etq5n$z33IjDLV-c6Yk_=&W%@fh32WqI;;sgKPZOQkK1C-djEzofGm+M8P@7I zxG0W(U>3{O)HfU%?0Q9Mh#dj>--ep?knN3F8 z#pZ*q=-om{#;HV|b>6pHX1xdS*3lOf-mY`Oi6ia4U!Y<>y&4W#9Ze&hmFKD~@|1zO zdHeIHE?BBCAX9;1*Rm2XpmI6oTAK(m`V*4Q9D!`t$_06L&=uMKiCo*w)kb?ZahT>K zI-b`3@vh5s-sQ`wixaE42<8LaiB7b-E}BXhTFxg~|B!ac#H>tIK0KSXkgl0jk^tx+ zTn$=Ldd^()&Um*hw|?>%I)yRuQb97+r(|^YWW1*)`H7z&AkVeEpx(YpyiHd5Qsfew zc~e4BNdD^UEAC=ej6az#jzv61>%?(;^d~Tg%uRn`B;%cuV7^9t$EQ;>|HZOC3Q^BW z??TlLK3g<`nXel(nq=j97OFh~glu-%#qWIL6*Gt1CCOA?o*_$2vZcndO)84K&mp|)3V{gded~Xyl?5)f>|9_=vTN7 zxVCK%2U)GiFE@nfuLU6I$Tb*5w}B5+D>+g4-b$y~&YSiEdJp<7hR@xcIP_0GtL;&a zNB-Vb@4&~LFAQ)dyJiZ?Sm{p{yjsYG7zqF4TXa!g;<9_i*+nfkII9G}mSMS1JgiAT z70|sFStJEhjhdG^muKt83tJy?a#5!>^_6UI=*>2)%wJe&L+^ZDAN#U)DK2}C%yc?) zi^$Bi;C%psJwHEk!`KL>=FB^FZmdJ%KgLjrzQ=*_8S{&CID@XfRkMCSTf|PESr8>& zB&}h=8KrdMA$9Huf1kr${>2Ko8E=;vmgLrqhE>T#?n%a2j?O;J^Q5$o-+k%G5qxNMs{lQapFzHI2ff^ zuZ7Xm{F^jgBeh;I+7D}0o!e94DHHzX;5srE_0;WDinu~U=%-O}kQtKlL6N+rdV;l?}07#Mkp%QH?XVZ1}m!s;w!8XeM{I8Fv`F4KG zWNsJ9zjLJ`NeC0w*$l=tj_G*#t=x5qxhJ5X{U}t={Bt>dp(7S)H&@fQZN21mw8j0JQ-7Mf z)j9UZ->Cm6UswB>f(Mq!veGjH^O!-dOQa2EY_eJV^H!ynD+F!&pzu#xZQ(qO>ut#t zy;2cia3?>^S%>=iDI)%cxQ`ij+dC-xkKk_D?eN-rN*CA@^F`vQP*@oE98nj@=Or0nVOK zVq`MAH^xOnc=cYl<`^Oac-M5FN_u4|SKT5N@N;zcpIsI`OyL`i)^tPXW!j6mYS4b( zNeg}7vqiXR1pJabqvIGZ`|aN19TS_-NvkDu4BWHNzg=+xK{D z+df5rH=okO#(Vs;*A|aNOwOjjSK6+J`+8ot|IM#eiq6LvF*_CUO4xr|>6%D$zO!z} z%C!<8Us*?|t5$nNALBysM@sKcobV7I`34SI3KN(ba@VPBgbp~pX0BsL%V^XgR*hJE z+*gEY|7c4XP;;zJ0)9*ia_{qI>)D))(5Dyp_A{@IT^4v6A5b9!7o@Z z&idwm?r_~!LG|imrbi_bqEzWbe-vJ0W`{RUl|R!c4m?Wwz5H^F@;u!VaTE%++J)V& z4CoxPO4p)Vm8>Wr(btIKfw6b+ll2ED7|4bhN@y?d3ZLTwYC9&7wrztcpnn z0-=~7xmfM}->s5)u{iH6j=@JxE=h|G_AYm?$W?hf4F-?da?q)y3hSW7stx!|*biEe zX@5iG@U3P={~o1nHl{SjiU{ z?rB5!AGKYJmYjXa+WJW9_=A&-M8U2P=7= z7kto}+RRk#Mlbu}7_7iVT!LNP*MJv1?=0ozVt4lHk7rWVIiXzt{3!eTQ@;nT&v0I; z=UySTUp{opB}^qiDOW6SN|qU%tgVuo6qnj}8sH8>!06o(qQ}MCW~nThvnQ{^2DUAQcAia>4L^qH zxEAN;GT{O$3hANNBDhn$Mht`^{BQqiQ{r$K`U~h-Z+s>$VLC~-&PW0#a z8u1&iSn~0xMbEuF$lW(B!NQInzKSb$ULPpM!Oj*r#$)?0>ip>I-}@YjDxxvJ37S7e zM0;0f30~ihqkDh>)ZQgI{-I9x|B5TLFq|WS|1@42QHXm-`zAb?oKOoie_Zc>Kw8eh zPajkI169gkkH7NwyDlz!79@bkmt$W#*CS_(TTQQgIDaPg%?I1&o4wn;84^>$KcLK; zvjhmkV&JACkS4Ip1VHE zG;Z&HAGMUmA0jZ2J)y~Kuu>5Ze0^aXseuULbhjSv)1#AdtUUe*R&CO)hhFvq+R0L_oF0FhVK*|2I~*#_o7`%9wDaLL9c&uFn4p8e$y_9vk_*(Lw%+0=^h z^klo#lIqD&z3G7s|FfTT%9CZ(9_J2RKljR5Te*_lC3%WtN`V&7>JK;q$L<$>Ji6) z5@d>r{Kc=%;%>_ncz$(XSj3unmi-lX|E#_ZrI-mdbuO{|l}DcAx1>G5(*TKhNbyu|ht@_#OD# z>pockfIUD7{h1G+?N4Aq3(d(2%53)DAv<)Q21bM8o8SF$ug+GFy>A%mLO@Zyx9ZF0 zaOj9r9tHY9R^dQw+WvI$f2QGN#4f8Fc^9^WHyk@TV3kVY>gAe9?t{)^>!Yb~PN{CL zGJzaQQ@+g!gJhocuQUGFiGv6kN!jn!OSV6E?3879x#&h0UG;R?rVVGtU9{}OUEuj8 zNT})q+;KIQA{afFki+Hpah%m6JzRtPP0+{>_l_Q1M$fw&ws*{&grJ9e{*e-p=M3-J zjwtFEioU;6b7z>@qK4z#z=>$lQD_YlSG?qLtr|p#aPErw_lZC7afRt=eH1| zQ2OgkPf6*eQG^X%De;m5-uZO__4DTMR4wGW!eWd4*8>l6WCv~RXAr}urNr0ud&{4v zv7#1)UA)o!Ha0i;sju!2mFV8KyO(|%3R4RG44M>hvOA2Kb@2H^Yird66MMeaH}ZC# z`~iC+rAcSLoHk5PG&8okHGW7q>EZ8TK9wKhd;=Ejvf}}a)Jl`v_=8CIm5O}fsR8Ye z-tOI;_||dt^VS9*VSrGy2yQ5lyaSI|Q*~Z`^;Ti`)>-JP@2A;9{5kM++ro3K3H;Ul zO6|wKQX${P9ZpMhUpcqG0*f@*^iU><4p*`!xEf5#ME*c}#5H%f{ci6ZI_1rkU&K6u zCw=T&a%nSh$@+Df4yP3`m(2fmzrU{%*b(GTa}jvgaHWB8#S*RfxFKpC7=@L{SqVQM z%2V#~8s+xu1=bqRf_vU{Ii@Ds?xaVZRmafp?O?3gzoxX5pJBI4^@Dk!tD`N4mDT;` zi4(%97Mo`Tb(qGPdTW|YHqTw}@qDP-E&ojaTs@iu$p)Cr2Nr9>sp?R}(*|I%A|Zjk zWDcG|k5M!a|2sg>@goVX;>d@XBVjNt+X+mM6+y^J`Yv{_vaLqSE2{dZHlI8P{;FR? z!~W96@=7|7x;5a~AIZ-!b(+;#WY-ON0ZP|@m)vM%x1t7%SH5+8x3n%s7U?(dRU(ES z0QKwaObM)hLHbL7s0qq11F!z{paVP%qkl4ui>qeQ)>GonI9bTIjLxV1^A=aV`{d?G z*jYwtEO&Jw0FlxAsnEu=(R8zmPVqzU$i>dU}722=-pZhzma%uivrQ z5?0);VxV^a0r?*nhgMRZ*@8dIEuc6|7z7Zbl3dGPqpUHd>I8E+$B&XOzC+nG_+}>YiepAk;OK zU2Nen-4BZu=~UvVOa0!V8?T}P-}PotQ58k5|C5HvKPWltJ5cfYFR#D7rpF1SZzBv0JFq_e_F^F!3% z*!}bM%gBA#$woUxKPGyMzjNA(I6+q741uRYO=N*R<TBLwvt(!Jv+}yH*ueYV zV{Bf-U{UQPLzSYe{Rv;vK6L|dC*z7(y^p<g})PEWK=gzibh;z4HlXKd|FAeiYp*M*o$! z6EDjCtabdA6su_R-oBWm>-`z{oP62t37b6@_U^%GjxyJb6m_odrq%uusu8NB1Gh32 z6chzYF-_$B9pm{B)VOc%kEmBS z3jkgmiz@E+`t;X#oBN2>SaKg+fa#{%L+4C!lxDyeSi8R~9fd-?fx5!?Sw9!h$KHV1 z*}8!Jbym%?nSF~{-1LuSmXdC+XNnIvoiN>&=e~~04ove)Fi!zbID@Y>2W$Q>V|1=U zyNH`>b`c0Y^U`GV}GV;{>!edTjtUUh)%P8!EBafWJYz_u*_L8s}R<^@*Qd`$1R$|A! zhoJ9OIiEuO`7%=D#kJRdxoYfi;ja-5OMe`@UOZ7-!I51|fP?t#K5tJD!4UK^Y^b9HXgyv>uDdx#Yo|;s7ojEh_h?BioiwX zUt!Jh(hBQc6Ii?K-F!9j8iU;|Ie7H~V^dj2d8JnBgO0xx9vyS)%gu+s8ooSKBHy{i z&}pd-HDT9N1)*qpR8HX%kLs00Ow`~tr7dM5T^4aUDEB(<5n~;X@48Ty{AAqYPVMnw zlp(BZ5z*T(n5|y*mRTqB=wZdJ??V>T1NofeApneQuyPhl^tvPvy}bz>TTTAlD@ExD zPM`cfo9e$$U#h?zMK!JkQX42)=@@PWx(oG=9PYFa8xVuh%d?Pr?>SWiv{g`YqPktB zyy>k=s`a^RFRm#6V7M#NoGEULR=?G~x&(UzfUfe?PO5iZW_csMTI+2r=W1H9&Ue&a zqs=QFZx_2WimM@1spe>7UwzRct-Kh8pEe%>`WNfodLa66$GVR(-cJ)uVh+Z4W8{3S z-JwwU=I^0Z_lv%AfKg>m*W{H9A~FtJ;NCU}!F<)BxZAhB?{)8?`{leAM(nf=Dx7yX zy%(rxKNQndd-&aXpZio*Z&>^rSk3t$9#}x~aRMpgLoN3BUgYo{Pkn`0Jh27X(H1Cx zdXa}56O&u1LO4JRB^45zN6XF^+C8T7==l1^)fN6&H9H{)HwN0?&MTWU+L!$_1li_& za!^GWvG?1i=XppJ5!)euG?M*G2_`EW;N@gwb1Tq%pV1i@Y0iVv=2KR=^4EIV8;^Bf zHE9ril$6>{S#oywGZ0+{!It4i!DPfo`*PWY9pxT`4@vMyX?3f-ZPl%Wa8Mg2D;{^i zbbrofbsN&!K_O-g=9`A+V4>#~4<<`VpxuE2&po$B^(;I<-EXlZ4rA1fnDvM9RJVU_ z*t>~L$a@n+Foe5vt+yKk6|j}BLcq_2VChrkb`Pcd{V9I+5a^iiKmRdEBehrBViR=& zq|!OKYh6Gpgy8Z8cuF4ujlhl$PKLIUC4jeOkpeq(A$IHA@-oNSyDY7Cgy5*0C8?Hn?>s1M%=eI<*~Tma$??p;AOC*E@~-Emts%X! zjxYE5)3O7v|I5XihQ9gLnt}D-_36I#F2(PVddY+Nc$EnDk8bvAt{leZ>|*sLIW zD|L-_yXx;pe5cxTkc#8kJoq`}`{+^9>sb{G%;_C-ihJW|UaZql>>(?){U%|~xRwV%E}VRByrek>vy( zGE60X%nmPI65K4FdT3a>hV6o$|$lwUWiAUSmmVP z?pe2bsFdM55~FN-FRwEP#d{$*8zc2E{VNxjWB?mfM3!h6UB7;~qv-#bDV#~8SGc3w zq&<%|;vND{|LeBNJJd5M2@0+nHFEti5;Z#dE?o_kT0@#oN{M6a5sFA~9?pP{eLjb< zkn9~|EsA}S?P(Qxr8m^sQ97b^Fmi3x60bCD&VTg2?%|Ko;o9T?_jAos4{$YIp|RG7 zMd+R@g}ApGfo{5*Ujc55vuUHZP@d^-b7AK)fsJ00$}TEkoEFIn8aB%*w>x# zfJdfYV|Ns$^v3M_R4+AGiO9CuTsNSN%4-lAn^eJ0>PM~TZ9Z!d*l>JidnEwh1Ax`c z)ZvDp#A1!|pM7CkEo=oftbbU+)%G34|FGMDp#IQ{-f_+ibJEf__(iJj7x2_S7oL>v z(gT|x4(+&WAwI6(M^Q%)CICGj+hRW^hhmn3?M{D{fczWad!gPsOP8~CMUPC~Z(QGJ zVCOXrYvwC(eAap4uU2y5H|#%*DiN^Pg$MoWykYsPpst`~U4x6J+GMPKAOPSOk|y{e z1`CJAP2U$q&X70dehX1lmba0xjT@Egp0su5hfx99^>Z7Qo`S|zZE*Wz4R(V*hWwJ^ z&54#gN4NeC-{er(U&+05JXHvBPxNx$XDiI~-#aUh!uE=h&jyDgWw5M_oT{VC zKJA?r?cVy+wALiCSx{|*L#7APtB|e4dyi4NiKTEd`)SyaEQ{xd%7lya`o zMOu*})o;H;Z>siY$b%D@XPC|N6nD%46}Hj4>V!F=()Sh2M0T7kI$p(3<)<7@TB$26 znXldp-RXw&YPm2P{4r#!nD#F8S!k_eGPOTxB;a^=Im#5y`aMLRGMfBOT1e2JGa80s z2WIrfvtP27`oMkq3co1KZMxAm-E*{02WpQ4&l@yzBs? zEvcHkT_Q*N-W!T@$@@Fvz=FpM9|p!veDN>4u}@c{l&)Q*G9*sL1!t07q!^q~_&UXe zf2+#;XFe%x9fB>gXy}^J)YXA!^W2whW->-nD)}5P?0*4Mkz>pGcsG|TZxDZe67J+ZJu*d?eD$lG6jmS`5$?!x@aw>p? zLEPe$$vpikh{YR{?~=-?$)RyOTY^-;l6#tGy0S1F@|%9RPnBT4{(fZF!8G6a|Epl| z;4O~)msR%v*-;TlGZyn4chKr8=yCebgxEFZeys6yEc>NMwvP|5Xc;(`czW;^51Zk;mBjpZ?$VuOdWe5GQ#UYA=kYOhBaod;jNK)a!oQRD#*(*<+$c z_}FgtcAcb4Q^Z<7zhZp%YZ1K-IRD|@g~Vj2d#~y3saoUDij=!vI7X+&6m{3LptR#@ z04b~j&g%a%z^8Gj#$=YG~Yq}Wq>DR)r2{42NC~}&DcLU7PW0EDfwJ`Ci9bnx<%5$SP`x_N5+^0VGJ~Ne2k|?AO8ocNxl*%$1o(S!OvK?OnP351dVs3)yJr zhf0U75dVnWC^n3OF&<2_{-d}(y3bN~WK&&bxm7zk_5i;jepb{!PR@ptZGRZX{R|Y& zH60IqZX`%gC4Acl`8|E`(^LX|ojvBEE*LS?Kj6TCW;EaiQR}2Bg<#2`t!4SSxtq=7 zf`*|P%Y61O^PT~|5{Tln%`8&bzIrPPhaSYMQ44aS}B&PeWMpL&NQ|f z>TU;6`{@y&K=({wE$y4+9vH$r+34RF8BHTKx6-Y+c8}Bl0ch2Y@BaP=xhQ~BLJ3qI z%xH@DRoPrS{Z@5bpTMi$;v zQO#^xDpSr&_NdfSRF(b(wAj#Z3${P0854ZMbQobOz%|&%qtLIn&yeYjX4Dj=x!7Gg z9q$A2H6$b(F^R*1@^3MFva4T|P05xJ&&Fr1Mi@8~&cy`;5w-DyJf~lBLlor=g!){r zZ#G8zMTSBmPXocna`sj}4gqvJCC&scq(B>y8%dL)I5H@Fi5%ii9151XT^OBX0IDY$ zAMo*>w?8Yhlm)rN<;-cyQ4U@<J>G64$s(soXOyV2;{x^_#X%=IUcccnIU)ome)4pHvn`$8+!OkQ_J^qyQz@d6X$lN zum+U&5BHDE`D0nQRg{HCQ@&QCkJbz6#%?Sq(Rj|@^JUZ-N?|7|eVz?&jR^M@?eb;) z0(++3Fr+t*y*r)r1gC|K3))T#)9bQB^}NSxWR$;y$v1IM9Ep3MsGgmd5}$e2%zM?{ z+V1_IXN5a8i{L<6RvFn0+g%2Hv|4hnNmN!`5Ng3kg}zNz+vs4WY@E)+4uB+Bz0;=Mz` zOe*5GTdd~~#)2uwSbuveH`$Xad3px!kEW#|PSmFZcQB{4bi|>l6Q+2!6NT0E^k*;L1n1E5cGn%fw@3oAq4hq1@bQ>vr8&HW%&L2uE z>%*^2sOeS5OC+w7#ACN!H58TqY*dMSd$1BJ70$(H1?+T9Tj6SSh^yF~obz#jHe2%A z(Syz9b8D)Je+S2ma_~oqsP`|9N87k);}QzK>HD{_Q7#*UuvRq~U~-(A308l$W?tQ| zHhKJSXyIo}>ak4T>fxFLU`qa~g1<*v5#C z!-4cRVyjp)lW85jn>xe{7R;!9Q2rVEj=h)B$jsHR1L>;}+ykEy`2I{iXKKj!P+q9% zhpiRGbpOAWS_3pKMFqWH9KvRfQ%j10k2A6H_s(%un^Dis`xuxnMYV^e3$ zT5q~E-+b$<`UOmw&fI~{156m;gsp5L##6$_r~H09 zT<-RDNnQ;*vku&^ij$=7N!pecfSz=`eEvFZnEAzd%j9T-L=%y(61JjvjcemPvA#Bf zp?3EU7{FDNgDvn(co4|Y2_F*O!>u@B;7n&Guvtd z39;bN8fy)VNs2W%9K=z+*!?R1ylLwIxLy0h)jUB;UY&V^QJq#e!aY4%s}0=gz%LnY z4{R!5@3;(lH@wmKwy%iuvO8Cg>OMJC5$HWiq75U#qL{82+Ow^xBY`A z%%HsKMks#BmI|2x9#H`}kJHYjp)sPECvMFTn#@kHOkY+!rWs;(5dNh3PJV?$0&w50 zKl!BsY;Mtt$ePa80_Wjm8cgL^9V`F*7d`Ohjo;w}sst<|Ho^?ko0ryZri|<9C7z`+ zV|f0q7m-tN(M3oMP$C+c`w<zUye0&m7Gaw=RGq8ZqU}*;UJ5`_VtAKg29e% z3Wt^40qw0U#Myvv`o)}(#SDK;E4VbIcvX!*WkZ^ZoKI~7&OxoiA(^S~AgN%$$_Mrj z5WTg&0VS?m=$xF|tDA#SVPf8a>&_+ZX*Z5UVIpeDEu7AKcOKcf1{N7Q0s)+Gw%(mI z`2gUfswOBbR1p%b@-{JVTk?QyBMA+X4s;*sIF6LU7R3qkiTym>&gOy`q`P0$@BY;L z$i=|dxC$z1HpN{o8dUBcAM_W@Qk9LBgXDRGH>P~@(e{xUz0-{!@n=q#VDB54=b^at29~4Mm zx3D;EUTyz_8UDK3ZdC$3f2YGwFxz!^{6VsIT#s%uo) zesdhwS7tTNwRVaMaDQ5!Wze%w{`p@6mjVHuTfMHYWvYLP|KO1FQ1e5(t*zzZv_A*B zkAjrXAnD}qnJrGF`Ex~>Y;<=TkZa5Nya%3^oTRcYE7dOpFPEp3hUe^5JP-@5fzN6a zQtd(&Tp8{PzP4;{HMalIXhOagTd^Js)B8NWNg{O$W{33@<*R~imn&xc=2hNW+zs=r z&|fD1b->2SM0T{qS&Q123?E%jI>-Fe>bKK8;>~i+px(ieXkz8EsmI=3LpXfJ%Mpvl zCN(Y^2bvybpj8Kx?NclV*X|})aay$LN3Wh!FCNUrQd59`72b^s@So=9A7mu$8Crz? zdZkrvuR7z!caLuCqx4A9ogHh~NVfU*4>4~qk^#r1fwGT7amrT6WPC0-x|UxWG?I>c ze*WbL`H{rt%0WF*ckYCvppGJzajM>X>xmG1MB}2$?)%RTyQzgy3cD9%TRLMLDyB=K zj~>nG^q4G>k;MDmH1CgzMt$|mjhkOmPVaYc^`ZQi1OYIjIKrPYp}7uk;CGs%DefIZ z$v-M@o`yPmQzniVXfwybCpt3`ip~2^2lz=wz7O&CrFIo(-^%el)AcSbSgcn%y2`;0 z%7WN3dfZUqJFd0}Ke8*#&q<5W%BbJ`a*M*|MQC^ws3@tYez76h1vLXcAx<9=!s_xU zqPTw?n6z=>3mrvE#O1gel8KFPpTH%>y_$2cUnDBy^{(ll(G`bjA03Ahrk{tjfD3iIP{A6J{7M@zQZ z#pE5c5E}QNPs?L`O-3!&7t~7|NdoCy+7I^xcKN*<7%ReF2}|elMc6%D z>Hfg2Pya`yA|3f{HGXsM4woO3MG2DtlqBIyhPaMX5DDe)pEMRKC&lyf|H4ldVYLgr2+5S_Sjv#;t&2 zR%Hmb66^p+afovYK=0-iXV=Q7mo0-P5Z^ZJh9P}Mri=P|v)$EQso+ls>V`E^T3!Bu zBbu=Nc3TwjZ~f(R6Z-N^rK2Q)cji@;*RKbY>b=VXktj)cm$@;eHFe$)MgcX2I6q=) zDd<1hmA2`cW%(re+Dw7T#vVGRPw3J52;KNLqN01bU0&01wWo`9*t0ZZP}|wor@g=9 z#YBp43fL2`t|!AWX9%6ELcBN0xYSsKl+@ACcq{DA(W%CuREX5&$IARH(aax9f{om} zmYAcjIySQAA2^9D& z3is5U*3x9C5bUWSKE|hruBaH}hVr<6Jdjz0ce7?3T`Z{OKG3}dSiJMHh_hiAmG&`E zb}To(2kFLGm}d=i8N6?}`JI(-*8T$(GGQ1Cz!6iOVU)5ltDQivy|ZNVhJ@CcP+#gL ze|#P|z>jKqij7(VxcVcZSB5R_Ekn--Ui){cocjFl+lsS4&|ooGTY6M6BBOlo%Q8uU4X548hoI|InE4q&-z=*ftiWK=cbZ- z&H63i3oHHST(RR{=s{ceR+pkko!IA7O5T-RjQK|g)r_VtL1^EOBkIU(JrVpjn4Y=2 zeF2=cE^Ee7&E+57@%-)uFu$X59es`|u#@5?EJ9mr5EEeSqwv0rJ`*QAxFFAg*i?L) zL^vsbz1f!>RoP>LkjqiMzT~icD;lVDp~jFe>%8l%yONK&JUN+RKbF;^O^eWlIWF}1 zRDOZ{ z02k(^?_8-5OMh%p5&rGT8+WIfq)GDCbhmRlzPG;HCqVR6{Y2%%kds)MCMjC_Db4iU zX)x|uy6`C+hdWKWj0_EQQl<^bnJ%{z$whuO1Q$RzYlH&HRa{2`4I(mYGf z{)>y?0338T0jvvabzrw6G zYm$3Ah0-T#del>D)2Z@ibNt=TH?Z88iWj2&L||NVs|!H6UU|H$>_;x1KgqvOH3bK{ zw%_>@y!!Wkk!1D{?;c_CvCSZiouS8tmZT1XT(VQVwN%nP_pl$O@V+R}g+pT`!#~uM znbrfAsj?|W<;S}Ys#d#}1EUQ3A>ZZ=6{+%&^;>oZ#<2sHO`g$z)b@;eIJw2Y`>NjP zMITt8Rm@wYM;Q%oxN|T&`*IGm!Kx-dkmVGJKjrSn?7wsDl9g-kD3IKjW)H)&^BAtM zaX6DRQD0t+39YA|%(6YD^a;1kJxT9k4Vq16IN-xvFmS!wj*??1&eetSX>q8N1&XiA z3snDhBHrwcza+u`_JrT=>bfW4RpW8{fmu@FvQ+KHj%EWX`xA6zS={1Ff$N8xQw{TD z3dLI3FUOOHAISJ1U|9}|$&J-)^It|IkmWOne*khH>vWQ}clSu|c!!pZC{TVr32ojao907an$ZP@D*W zj-p@L)qc0^(DP>TMrPpI2V)<)$<6|KDOJ#b3X z5#!r8l&ItjzjDTTxyJg3m~UT~epLvIU@{kU?~2ZN9}jdMiG5qn2~TZJE^xQyHjm}h zH@7F38EzOyh1EZKB!n>Y+^zS@((*eq=6>GHe%soS3*-LHnsc)<#?7@C#p^>dWIY$P z6q4)|wI1DYS&we6CN2}<{JeSY_Bi=Mux`v<@;pzzdhuvWcS?T2>{R4u4&TI_JE>>2 z8hpftB7LGifORk1_GDH*t^F|kZ(gJ~>Dy5t@@E?V@)Jlx?8e?b*3t5OP`5yDo=wmk zm-Py(pv9Pe+Qf4mTg~%s|9G;wz1~BI+uZ>{8@)GGhYPtAcfq&(2I>M0hH7V)%*d^l zI5%Mzpzd{x-n5p&o{KRH*)tjPO^J_{nKPFR6*Sux=03%8>NS~qQ$Tqa z%D&nc=wG@dU6?k>cMaUWp{bHW?2+HH=9KTmpri0NZ#Ynv1(p0{QN`$Z=eTZpciKwC zYAPD|$DK<*|6X&6{bu6IxCN9+5|a9iA4H2&Q)rVM>-$zF30iH!j`!uKm|t z=&d~7qAX4{TFw#4Lb({2=c;p5#d=45{YmUzamaShq&o}^~uZOqD0~IsTDQRc@g_{;^-o>bW+kajLzSd%90e^;xsjC;8Qh)2j;0iE-=)ujdJVsXf zXI=v1=63;CGiY{nD=+VCiUr;7m?1GD!o*xr4wr$p?7(?XFF7la64b_I7bm<68=P`> zGHPC?qS&e*Ol(ThxQJbY>KyC#1q#Q7Pdrx4`{LIR9%GI}(e?)W8nC~%O2r>u6@EO8 zi0a-^8W@L*VpI2wog>e$ODbFs*_ien7JSwYCif++bnVEjjUp^uQT}(cvJ9?aw-1SR z#zkC;_He{8B{Wo#1oPk6<;SG>3)Q{#R+m(O{^ zTbINl-0fnpERe^fqFe!ZL-L^J>t=GJMC2Bi_6M#HAfJi130TW;Fw5BdH6(P7ZHg(> zcO*Zr^5gR^(G`zdf#eN4a5&n$y06YZL(_~~G&|m)&lcMxssz$U0*tbbyMCph7NSQM z`DOpH-iAije1#x$wF{)YFUh1H zTQ-K`f94b70RsH_EGFJ;vio_$TL<#Gzn!#(kbEI;z0D;d~&PCY_qVwt<^ zJMpy?WE-`{TxnA}CF4QVo^cZPs7)WEP^e*eG%^_xo*-pW60E7;%9 zVs>5C>j?lGoEL2{^^oZ?0afd5r+4*hnmY(?^gjHHHT1g0b5eW(#W3Z;SGyVy*x>Nn z3h2n)`DXzfS(Q@B`x-`1kI&he)8m8~%05q%7llQ#mj1NYovN<#rx+9#Nx5&z^n~OfwG{^ z&!+&TkH~mtX5}<$PlNX|DJHWH`TUev{?h?<1rlM`_dJCgnAFNo^1VqsX*v(GTd4j~ z&CMdJ)vo#SIX$NS13c{NAEi}}3d0Gc=d}$p6DEdtcMS{cHAq>xZX=x-5~Q1AXH@BV zE5d%F*>N5o(JI`a&#%?N9s+7@SB3Vly`b%em1X|8_Ls;MUEp}rd14|xJycd zJ7fu>gJ~0&s|}9#0^XR0`7SVHtcHLT7IBvGMh$bHXs4Wwn$enS?+0TO>W1Un$Xx%5 zYssE&!O2{!?Oqq3JQ8_G(*nbwCVFVbh{wO-NDYM#?k{Tg3|&n#y8je!zDfJhu32`W zLl5~j**q^Qx9D%)81F&F@Xjum0iRv|OBG3uy3qT6&3UsN?)g(N7Fixpl8`70Zte52LV+Yr^q7f{%|chYxdbIn3xq(9dA-()NKC z%>i`8FsBtrpQpBT<_vOnxOSEL-K0vHRLBd?%C~vuESegc*l;&w$K~o*1Lob^dnb!x zrLVFR#Ze&1z)x&HWq7ZAS*p44bIlCDO}EqJc$fTDvg9)%s?2uyU1F>e^nvk1g~Wkh z&U0*hIfwTAe*R>#B4v0{(U%d=9?tC~PpT{NOc?49Bq?t4xyT!gD#oZ5sJRzn1KpXLoj+H8cl3na@HNn;y7q z)ew5Q@)?;%ggg&15+x+OEw!(A=lSTNUvGnp&Ek%U{p&^V+M=OJHzqJPf(@sJk zB=vXOi+@n|GDdB#P@>;yX_8}R#{`$mm}(bg7L^StT65P8vslf0cJlx)se`?uiM>t~ zR!z5HSpUNV0d$=N_Bq)nO6&PvdE?On)+=xwDz=fO*++fVqYf@pT;s+C$X`bh(;wAy9(QS> zLO)3Ij`T->WRjm;-chQaHC>gB?F_{#K$qq>VWfmu#Uozwt$kWe5MF4Hc9Wv8K14s0 z=wOa@ECROYF;cH^ZVfkxPHP@n;Jf-gvzFH|mm8)jS0f(;*m3FV1Jj0wf@M4GbxT&; zFn?JatqtZs-g23tv*6<8$KDTsubT`u&jWSc=EaZOljIL3JO(Tn9>FthD+cr38?TwP z0<`k@e!kJgma`-73S1Yv4-)74L`eWM8L##X_X{nEm!Sz=`J9I;7dMkCO9PPcx;*>w z!wmj5W!N>o;A`oD0IxnMxkc9@hVI>-Q{eeFxo_ebf#r$tZM zNto~vy&%TwdG{2K?3n3$%cn6o|qBIn%kN!F?9bQ++C(xI;SJ?--)?ODjMj)T%_-1QT_E;Frx;uQ(n&Pjx%< z&Wjs$6Vf_gj`{tWW3g={PYQbes{VGZkEn0|R@#ZCz^}hzj-D5Gbb}!v3XeE&v5+~( zI@sIDSpy3$ZHY)O&-!3@m=p=*4N$F*2X0wPD&Me88LDMfX!*`!t*>LcUw2<7G3Ya+ z`C0@Cb7$`83UaNYG_ijo;4?#`A203PJJp379+{%v>Jf5=#PZpodZ{M=pn*K-0abso zOPeL25CYEXGOzN8w6LwM;kzWui<7jy@D_05S1vQ!ig}U&2K^l7+__wif;2p~-SXUFKul3pi>vVlc?f7!cXYY#nS34Pixw&is@g6e#3oWEgEDfvO; z=(c#%2plI!Yjfh?o{+q#cH?7P(~aDKv3>~ceP$j$`0j33Be$U>2eAMiAlQX4PQe$X zHeEISY-{gE5&ly&rXaQmW;l_)tmVBG(yT6N`_Y+j_ZD8irkWKosiV_7CUU~n2jAO? zwb_Wi@vOku$PxSg3!s8^!K?u-KwHedAIM3l$_>@A~t6R~|kph^3^4RAUo;--n6Xi&lUlr29sle+M|2 z#?FVzpxb?eAymj9z179d&CUuXk~JE1?l;CBW$}>P4(pGxo6Gx-_3xhUG4V7zi~?JM zPbCGVe;>AXzrZ{Q5^DywB?B`;G9d@Smff?lh_1F8I>o( z*F&##rk0Z!)x_&1?vj~LJ0_Yw5<%y|s+xvREzY+kn|Sw63cgwJ#`9^lC7;8VQ9H5^ zSzkLhc_V{un{f+vr1J1Q8cV92iQBBsm0xEh)kP&zqd^raXWq5nfKe^}w}Nj(b3;e6 zD#5wXwTcoK+V=cMFaf|QPB-#f88SGC(@??cD#nG)Wxqw-(c5#PiSAN20=Au~)xG&L9$fg7XNAGx0r{gw1qXgzW2x5R!dohJ@^?>}&QVrn0XiLb8lq*0GM=3}eh<%skWQ`+JV# zIiA1gkLx|xao^W+pReJ=WFn`KXl@dNJA1FB#k<-SCSSr?P{v2ZDMWaB6F04QuJL$LCVmm^;gH;k0M%51H^u zV^kEcTFue-+yiYcBDS|yrQyJZ6Cpc;vViUf|K$K;%v$&Ll84*npY+>Mxm=`kav~8< zFR?;@{jb{!GMrv_5) zv}O9hZCn|-K2x%J9$DY8}1)_WXnsxFIdy&6<*zUnp~d9mrFVsfpEB4_!^-zC9P zxjo=gYjDLSCTgP5ibl>Ero=#|f;~^;u||;@$ox>wL+I6+GD)nd%XIX#C+VT2EH)j01EnNn8FPQOpx9T-d5qHH}!O3#3GJZ?Co$ zZ<^h+iEq0EM_rc7zr(o;W1fe+d z&)Y8!?Pu_^^GS|xnv~y~gc3R1c)e~U_rSf8F-am#XC(=#X2mkI&zeXJRnbm{_Lje& zgk=oq-Xo3o9*&=;edZO0-5txzRLWSt9F6LfD^;@2%FpnUq1da36zROlk@_y*vigI? zY3s|b@++fNiU&_$sLVUn*a|@ZaZsc58~Fja#d16xy(mghwmeT?2Z$Aq8`KZ(xuU-o zY;AujO68Ce<4gL1Zw7_msGVDZTdc1DLnhYGIu`|PJD4|-27PZXj%PlYpA}hpVRfcH z>B%w6vU>vbeG*@0ESfJWb4fxYmogbYnVm619=$$iOROt>SBe%9S8#p14p5fw*g89T z)(sDe;NNXKJ_}*`D)db3&nlW@N*!&=Ey%T%We9ljI~o{sba^P1*t%>ABFPwy9FFJT z$f=HF`xUgcRxn)`dIKr-lwB5i+9VYFe`Ns*{xYBv1SD-HGxmtgu6Yq}2w1i)GY#k8 z0(9FWJ6C8!Sr>#WI8Id83StqFrAl>#p!Kil**&=70qrs9GIN%4ziuE;8Rv&G|LX2< zM2o(JPW_IQv$nz?mSvKojeWh}C4~@KdX77SmrCwJ7yGHhU3pD3`-4b6f_mh!B0)Xp zME!`veGy6hRhe`$st2avgQ7#M#-USI<=BIRxG28F1@+_RYYgLV#N`S`ZBNL&Fm0=_ z?m^y6R!#7=+T(~%rfapxL*}5#_JJU@u{^GyK{#WVRPMo-5LtN7^~%@R9=AF#+@941 zA5;Z|beN7WD}I?SULwmlt_BsAN}_$hI2qkXsC^#9Bh#Aqb^p;4iT!~;woBK;Wjmv{ z_Mi2Sh1|o6J4zqyyT@7}&3{YU1U8xZI+~2XE#iq6m;&T$UOSq*8RcF0=i1O)t%}euKi#Kv%XI6iG)k;J>0sh~(`0T)b$a)sJz{Nl`uYl`N z^{rS129@?$r*dq}_LiXYGLIK__mkXm(%v6Q24W(&Q{0$*u`>3V!^%@44bHR z2j2FEIt0>?6LmWKV@RU_U?T`D7IQKWCJ?jqI`*mn!<=(8fj@xZG*WXRr4ATO8AKh60qE}T-@^3O z9+)e#7WWRJaUk|&kQ~JR-GbptN(#pvOvRJ3rkfvJBo#cpw%*>=^N`;4mY)#YpSG;5 zP=!XhT=4Fsg9ok4IyFsaT1t!NFsrc;U1IE8Sy#02(SIxi()>D+%RBMgPkdRk?@Rs1 zE)_)p*Mr9<;-EfhqsGGqv&&~egHK!?CTYtN|pPvFO2 zKB*2oym(tMQDFPaKZ#%Ze=LOGgC@dwBt5#r<~UH(Ap??CFfj11s?h)i?cjZLQ;N%Q zZ)tCXxU$n7aP+Owa7d7PjooU>f|Z;hd(NOcvl{4Su!*h}#laTo@fQ7XeBWp8R66We z1z06Uy3NgX(N^4io$C_h;fp*KV%7rxBX;fTR}10sUcsI4o50YA2v^72s3i}X=jhV~ zNwW$58&3qf%%jvdM?5hu@YrF{TA@BZY{-$Hld(t2C z2_J-Lu8S%gK{Gq!NmuTM>7n^cjX_6Q@8c3Reb3)jpRW4wsQO!GUo+MU#7kWHCg4ka zwk2CMc`HZR)&B4LY3x&XPL>yk_flsDG`SP)R?$-tIEiJ*BVgB({@2rN9BOsqrgzUl zwJ^r+bY7)#zE|6FZF#ze#irjqmy{Fykk`d}-;^YV6h8R~gc6(S=XZ#g0|vJ9kAa|J zuv+58Js^ydfWi^6F=X(q_@`XqJl;Mx{x6%G@kh$7Tlk{!hJ>0kf<|<5l9}9eg=|$j~SYP^D7z)sD9&PV-r$ts!zQ>af3_x0qd%kU* z7E8_{5x-s_aNe)ft}6u6C92HTb0NGzenSNxFG5`#j{YoJK2>7K0YJ=xQo`gA0=YT~ z9@B#ic&CUUML>MV0Rr% zMr3^2)@tldsR_SLz+_MQ&6Ty_EtL>=marr?3f446IU2QmZ(XYX{*c!i%4$e#dDV_^ z>*Dgr!xe;WT9XUra-0-t!1Tq1A>x3`Q1xt$$+S+$lYyO)iuBwJ0pN#5@}u%XkI-7> zxP)cT^d|Ya>N(0}eJTGPjoOBtUPz+_V;0F8&e|SZ-(g8^+le`C5U?M4q4X`uwQ}n) zk=bazh(7tS&Bt+>##`Z~&(&Jy^s^Wv+?yF(HVjGO!WNz6?6(PJ6>$~sdE8c}jns(# z))qZ=ra>QcW-D1nA$JyUz+WQ08|B)IqHES&S~r72i2JCMk!U@NJBmUSaF_I4Zv(rB zp^lHe$;adFA=$w zbEjcg%dsiw+R&`WAcT?Yx+AGY_ zFx_mTN`wVWC^Qq^vi=`gpf&`jD03)(L-(th(#LfTvp*c7c7wjUu&ir@U0j;=D>CY=;jUsJE&S3~enjW0yZ2Ti05Z zyS~vT01u3(|L4li{c>o=MID45MYfr) zP&;5|uthL!Norf{P>$Z8y;Zl`M&BV<-sVX`CbVEFEh}no0bBVJI$^9j9>^HmAZ_2G zK$(G}^9gm1gxw2)7ITbZzZ?5$-CDdoa~=2cEa2p2R|kMK2Q(_6oysnbbQ@1}5wf5yGvrO8alseW&(lmCp`zhSCR_1S-$eFCADVu}nI$kyZ;}$V5>7kmRrgFK5 zKU*e(?vx^6jP=ghUc_wAfDw*a(*16Fs!HY6WwK!HIgvC{M}W+Wn>X8%1xjr49t0D+ z%Avz78Exx+Ya6mb_f~BNFwLZ#BnTkiYuHH!H#>HXl+&*hH2L&G!1zpmrUA~%*r;!9 z@A5gBx|yVpq)2X`A>+UWMzex-2gBr(H~x*EdpuD+LBHcLykND@qe$fuL!5pzM>%pZ zZ~`5#VWg8#-kZ(Ve%gZRNL=}oK<(8IUdyA=vYDSSZV&h)TSuseX3W%R>c@3Id?(=| zD9l*X^d!R6zRWZ1@bZJW1~8j0bp7C?P+p1k{T1wUdx{jH>6hueE-2{dAP8!$ie=98 z|KPKhn_{;UEi>||droucR8qkG(8rrj>NK2C`vEnnFU@M2jU#wvo$ym-_2h1Y`=VY5 zrV@hjDS+ig`>}*B{|JDylk&MEK#dRY%vx?efT#-qc@u)+nI& z8?5rmq%e4Yh3W@;rMX(S{-&D39FHhe+AMA}nJTIQ7VVho1~V3zACDt}q9yWOF(Sz8-AD`C3fM?aoTID5h>Ap4x^Fe_4=FfPd~ z)^8|&#$_!xgg?y6EUBq*@4GOvBe(cyxRCg9k?MEL>H4TJ`0?kC9ck7ruUm4OGXRohwrJEj7U+hCdUaF)s<$@bRvf_P z3Y%Ou)8IS++_(CYaw0lzbWT0v!>9fjs<%?&04lhVp zeI48^lp*e_SP6-l$f$ z&{WtH(^zJ&Xv1r52v*yJ&N&rd@>J`1B7RIX4Ye6()IiL1#nq?$1<*7hlneQ&1$z9P z$0oajNC~e~kH`lO|8(*0U(VN-`V!zO!S}Ka^*=w84jF(qHxd~YQxhC5$Gb6B@}bvf zN7=F!HpKwawSF+dD}@hLB0}>&&XFf&&Ii=~TpfBe$ zbdm*O=0Pb8Ab`e$?5K1|Q!BPq{Jr;?SoPmpd@#>m;nr`fy*__jM5WqI^jnwC#@aIM zQPHIuYp;1{?KbQA$dhldi7lh^jax}dQ39;O_5v&X5pD!AP#(N)iQD*G#!DQoc!|r_dtS_a~8#~or zhc#a{_J=bPE5X@MveT&5u7Vvi>2SMSbxX=#Vd$n3E8o!jdck<>=QeKAhuJL$j!BsW zJ|Qp}%S(VgZJ#~~{a{9b=|exX4=Sql1E5_iG;`Vt9AW^>rOncf@1BRxXC|oyE0K4H zq2~L`2U)I}vvJS=LTlznQ$mN~UN{chcp);jerIlGlbfU=eYIgbrRIv)y$fMcFuLl0 z+_9Ot=#*-%qpmRiS{!@V%DmUKO1(Dbeo_0;T%MPF6~yZC2X4;kU-1zw*9TJe_UH-d zEg4Fr#WfWhP2ww4YwGdyU41@8+MzdzVqRt?Kc)E9Hkh3{uKFF}C1!1?L^ORtOptib z=j--EK2YpW#+Wb2%4p@|>=<($49(I~_tktb+dOb=%IfubcG$Pir@o*CpJ}SiyTh4F zT}uStrM=h5ET`8qY4t5|i&_LPeg&_1-04e})O2%`nYty{w;@{Q%Q+*&^y?4Cd~LsX zsynC_!THXx?c!RUrQWdRkKBH>6_yZxu?soY+S;yHvC3c~t$w5uW^Fv!atCO5{o?8Yt=elBdpbXnE$0wIWMd zk_*`fx(3ibA=pj89B(DTi6r+b+Sx#eFqtkIMI1b!ahvgB!J%C%NcE7_B~*D@7rb$l z{$!*-O1X*gPl0$0RhH2+G8iOxt&-Ek41j9nk4lzXE9`n7`wt6ZN@ZDZ$rsc0^DEbW zmzHMow~d2|>jQTSZpdVQ)Y$$ydGXvKZ=Y#l?naWCT)C7?y6Cru%8eH}X%2>0T6wH1 zAMJLS=Dhuu6JhM`GLM5k(zXtE-rGxpd6qwR+(>yT;}&Y9N<3S}U-xxFL+w3<+8Eb; zzPx5Rxx!9pcgwt>BTd)GzMWDo>SHAx#Rj(^r{ zz|ERX-o!+yop<{A>{c;cKoJD0GA7CncB%F=f0?vr#QT^w)S2*tpA zgoS}gIx%cH=%Nd6hQk$&4LbZj!{`1o_!4DAUQdf;)X|GKwxLRn|Dje`x#ZTw9*i@& z>Zz#=+3p6fB|X|OyDK!A(0(FuCM|bd%EF@kkfS2AzC$u6VS8kFzGYs1v(Y43PO&Il zbnTsxS>IDP*{I%B^Ewm^HvPb`Q3&}s=(kQ@IU^kpIg?-D<*o7$J2=yxs?;XZhMkbQ zR(qEBL-^ZOuZgw6ruWMZdu>AB$AzaSef7WlvNR+ftx8UgE>NBf7$#_jDMAnjBV=TJ z9_i(YQ}1yzN@Ko*elsQH{haFnz*biDV004*#MnI#f){v{Gu0X}hb}H@NKLg{CoL}* z=XjEd`7gfIV=^Cr1UXP^k?(-6@?<%3U2j7%oG>3&y^EDHJwe9?4x~ZCms#!UI{^kT?=2y>iM{kH{a1+Hl=A0SQ)m zhV=+#GZaFU+oeOjf$j2==fEylfF~|3?e1bJI($kX=M!mQo*4C5Q_BAMPw(0uWAfgj zsFO?@y%T6rY;L5A+C<9K|3}XsgPb~I7L!mf_xL)GJZ9q0dbjG-7Ks(xb2RK#y8C(A zRS?uoy$(h|`kcWhO6Tu%=DUZ0=aAm==W|7D3Weo=)^&WHcYv!{V5``%w|dlwQk{G( zgE?=q$(-L{Yt_AN@fs@^ZM(jV+z5n8E+u%yAJ2 zTWJm0kOjWn`la7=Mz0wt^ZNYKC(U1J0gS+!6RYX8_~YweT&;GsO+|fSw%7Yza?OQ8 z_apN&)}Mop-Y)v0fsGfRyApmldBX6bNdO4m8+Ak+1KKZ)2(JnRWTD2{NH*%@PRE&a zrocY$c&>|P1uO&(2*0;^Gvk!asc15|gR?XAgs=CXYhGTI42RDYf8XjtMF~O;<|=w^ zi@7;&70+ukjVe`EzETepP*>^FrEx9BXTI?GvN_Job9HM4Xa}_2;r;UUMH*Y~fhfJh zM%|0Luv&Er`@{b?3t&X#%>8F{5q*0xWrQNKS6A|NxU-ZBLIlKBs{#m~!i_)bL`(4QEhK zrKdM~!RgWommSpEhJD51Q}MUsGO{4N+vZ{m(h3Rvc8FQ9Jk}96&6ovm)o*>7@w>#B zHKPt&fNQi1cayUR5&Wf7K$YE?PK*VOdGf)95T;J&$F8mCt2gdOWy`Unwk3eKfw4&d1U1=| z{zlVgtzbJ^t*S(3*ng)?W*RSo5(dN3{Wwo)x(;pcp~kNf0}2_HnP;Fac%5%HUbs6u z&0+!fN;D?wci+CP${7nfpMYuafF{=i(hu3546<%?V%lzZMu37Jo6d4rn;!$;mgxZj zQzz`4TvIEv^gK}6_vPjwx0>shK9~E4dtWPl1!KG>@v$MS0iXYr2jWV&yy$`qUDn$< zBA=VSX?MGGdWUj$O#U#BOdXA4zWTWJ2fW8v_a8!ldQY{!I_a4wfoDDA*C(?pcq#wu zEM!4O$`9OD_V{63+ig(0Yk?!ucH&X}tK*)27ZC73Tc@=UGo6gX{A z%qd*A5izE?%IacoiJpv*UdzYlCp9c1ye}lU(wg*N2qdNbn~v5sxeg)QUOfcMQE(;+ z&7h>E$@6q~=|UC9UXl#)d}E^GG&zzTOk3?2Cmi+4allUa;sA zlD-N!_OYC!qXXk5ZAv^e(|_)({kqUsr`?Z_$-`FT(fj#%#KTJ9aTQXX^62DI`%TCn z@(A&YynAzd&H~PE1ysV9MdF+#%t?D;4_H7{)%SFX{^{}88+t`Oh%*sd|B3Y0``mFG z+#`gdrtV7Qj`45an#Nnt=&gigH~$#YMf!)_WRL6+(!HRs4s*|V3Ld~LblO+!57Ecin{)e+2cc$KBPr@w0wqbvrsRtQzYC8hF zdITC%kBEyB<{}9^P8!iHTj52#W2KMpN%=(|-93l1iV1n*?ARyBj#`D@D^+f3?S4sc!!?CRZ@hF545L_^bl6@E&qHu=g6_ zHO-Ae3QrV%;Csp)UNL`&x!`?cjjjF2IKAbkPRD>ffObCk+|P3y`aHdGm-U26&LZU3Ux$5Q(MqOrWH^P~vP}Z~)ORoFb#z+IKMp@eN(|H+FnIask-&1_@yh z|40W5@CWtA_*1WiEdq=LA(d6V>?a3&Z|=QShm{wJ+pyNl$k$$RCz%?;9%otOYFBhd|M>Uc$_!34-r^ zSPKfqUXp+*dm3X(WkI);t}9==IeC!!LbgoAsVyv#TI~+-wovr8HKo!;`Xzgwu{J%1 zf(v6~HWMr7qJ#rV+rx~X20HGpd=2FKpkl43cR=FgL(BW10++?zfzD{mxqDL_q9>&n zshZ!q;(7uYE7H|7nd6t+Hym>K6)qI>K##{;jr5og|`dzs-kt@ji|=A4okog(IL z)JkAvL2_)RGq+g9O(JC9F;>*464=ewy5vCnttu@xAi(7gF>-YwZdJ9Epkqeow`*ulXQh?}W#lR9QXI zoSAg2iZeZ)Y(4<&%0HDoW{MwdwO~#n2e>U~4x(VpPo3kvsoM|v5tGhND99?a)j(+){?*IO=j+4_` z?i@6d5gp2%zP|_osf`%FJ~nm{5nB9L!a@xS7C75pyWfWad!xOwIbK0|m7U@P^8?15 zie+>AlFU2iW7&|CLbO8>y|-%9(LV>}I3U7_GO?RHsxznr)=!EHl#9x^w7snA%oDyX z94~?9R_z`XAuMD=O$4izw&xvoD`F>5ge@H*7;XtO-oFJnUc13!<6@oewu^s0rZfJ{ z`L0v1e_%MD-sciH-{ET+PMhrG{^qqQm`C^Qa&K=q{NmrE`|H@Aq@U+P8nxrZ{S`@< z2!hIKO6^mzW=stkvDleVr8<=NgL6!S+NGf%if-i|bTM%>X0?p(%lhE)WC(tGV`Xe*0KdcOS@#u^b&_p9_4L@QJn&COmzl!7 z`(5fadrs$OxSoZ`FGTrQKkUitSKjfwMiLa42aI(?gYb0#fU!4XXY*W^j>Q*XG zn?5GDRQI}4GPKCjV1qhEneqHiO{w9_M#bXe{-n+7gao(?*%UhXd4r+?GRj{U(nXgJ* zY9c8L^>oTQJ1c|^!kf?5GWAc_A8GI{ku!2rU+X$sx}Gz%4tWrzP~}A8``dc74D{-c z>Zl^`$@9eZcqooGRm#y;<2^0{rxyK!K5}c#;$*Jy<~2Nc;^|Yk@i~LH?rgPL@X#)~ zPqc_K&v8Ftu69^ZW1@p=?9=ywBsK(in=ct-HB!f$h|s!x_`@_?I`qw&2k-v+8*JJa zlJ})>;L|^sCqffTeFmhkdoaVR@46=nG_!%Wb?)kfnxPE;p3}ktY%t*>aNu^t>nl4;5TKu)c zHg~FnGyFFt7mLf}5s~wTgFPP#^o&4LRn0zX{)~P!-(i!-;lD3OaCydDoLW}ylLf0* zDr^58yh3GN)JlHAQ@0^im0ceU47u7(5uGg9Gfhk)2UG9&PyK|UIiS5=h!X;BbOqRU zE&zja*HFyQ+X|g`Uz}FN%A;sogwUfr5CsUQXODg<*`0LH`oks3kjtRAcQg`B|Zjv@YoxeI3vRo-1&A2Tlw|Y=;qC1xx8#og6d9O z$R9vbB?@)VcJS-8^_%aNR$Y4!s|pX=o7WCfXy>3OR7@y!9Tc+mzswQPVPYeyqdEMT zHvQokxTK|sRiurt0AWj*5aIzDEOoGgyEB#bv~s~KD0wY2w3QLQki7r#ZaYI1^Y}t! z@s5`#W%MhT(0Jq%wczU%z7NDVcM+Lgn7IJQ?M#h|{xQmsSRFk39%)BP5mA?c3ej{G z6-Isc+ONa#ep5zRPIPF0mo|~U`#-@~Z2a$113dW8l22kOiSq~Iut=-Q_aiu0T2&~8 zK7BrnB^Tr>=b$}!8xq!1Z6meQkYe5fizsri3g(O^pEi1H<2~`b;)nW>>NJkg#2yVu zL`voyU24C*Z1j>I^Q$ZMfy8-ZYErD$btP1Lsac*HZ~-6}=M*mXTA1Bvu<6m3=#1sA z@es*1>suvh-=J1YYl=sUeim40TrJt^xjRX5=_*WPP9F;!*ad=|ee(jkF&D77dZigx zPGnZlQ~R9awNM$ad!a+IUGNg}y}S_YX(PdG7wfcC-^%jmK~H65t1e!mNnTNUd)lzO z+`s+IykQMrV~I7T5=?#75d*>n*H)~!-z=z0)*HgK3CGz}Sws$BTp6-zoi=@gJhF2f zn!UIJKk=Alba%IRbPhLuiLuxn5rVP5kB|wpPcFffMe|#vtbB1p??k>RiA!#F&3Irl zA~d7ZvOR!t3l7>FS}IEYHshLrI3UNrSxu2qv-?$1iXd^7Ay*TWag~#GFi$aA%9EoI zR%7__WJPqyuPOS|RV{eZd`Q6(u`75$1Q0<-tVpJQ+SLM!1b;-*F=y)rEA^0`2e1UE zCl^J>9=hKfNK>dQEr8@R-iFb%ZoIepp4t$}rw5!^Cnue49sRhAZ8O($HI?kh0O;Qv zx+sY6;kz0+Cj}UJ_d!bC{|Y5eiSSPQm$GXN#)i=wNev6Sv|oq4T5SjVsW^lW&{xNw z5p>Sgt(ym9EFAdO{T4S_ z#++{Cw|H-Sc)6t~(CJx39<<{Eec7?+!RB>FdM|3zKG>b_j)36-h{sFyHE)VZRcx>tW%}QE%OdLp zLx&>lne^a#hE-ip{&=>LUGDG%^=7t>b1py@DEPA3T;c}f-I5U0qv!sSfZ6{+RI`dyp&UXmVt;=M zM(gV8)!B>2+pRjt1fFPlxn35~J!;h{t>gU)XKxVL?guhsokY~@+ZtyvFcA& zK6kCh_Z9jNf9qK01pBz%O8%e7ER>$wcGph9fMDJVxE%6ROS!m`A?;L*tB;+&MnLEpPcF#trANSkA^ z*+`Al^9a?*32f6WDy4@}ROG=^<+aI}5`st`yS^)Gb)67z=yhqGV+{Vo&;s`vs5h^e)iJg_NS$K=h->AXscsdb(H6duHY zQ-$e4X~km7It8leCqd=Cfi0`S<;@n}GWhf+&ok0)g33YD;FV@n(Y`aF_I)SXj%PpJ zpKP3d{FR!*8rdTTMmQZh)=O%D4zZO5wXLUikRI>06~aM6*l7;7o` zf$XaPa6eI}umj{0eX!f5U|flna&Hqf2VDi3boTFR=TDJ`_HA2earohM8`PveQn-lwEQhv63_RV zQvH*Nu=>2tR66rh$NOiRLgiNHKA$~VYKp7X5JTwwIc6uGUA=BzW$0^hC#c$K$YTvM zs9YH)7haNb?C2euRV;fxsd$55+@TXdOG_VG~Y{kD{3O_p&9`s0t zcI#EiOfZj{0F0!@Eie^IQ!u#*~7)ats~cvk<-UZhZXnSwy8M^1S5u6I!1! z9O-c?q~91vBR~#=g{b4G6K_1CpVq#oh>iN6r5d}uSL@3R+wG^{nf$5M(O`36BWCFt zng!Yao&QN7i7UZ9-gS|n=&(EC-{S3QDDF+T1r1EM1n9Vm=I8{wHLJsvPcekzKJk%k z?H<&}e-e&Nphs9}xL;%&hsx(1|Rh*Ze9T03hI5DKGtq)}4(?YJP zQ(UHbhh=R@QwRJaxADSablb5|e(|ih@-|bdItmiISeJCeZIWIOe!v-K59l%8%q6kZ zBf=7b6H6OgXn$ALByJ?KSy-*PFnj7{SqupbWLInJ`VJ0Zq65#rDyy?NT1E*~08qc6 zJeNVontH+N{0o$=2(}p>(unw0gIj^G|gxmIa-qxM^%Hl|=l!3|j)PzS=g^o?^Ki3nUh{ikv-`8vI>z##8 z+=kp`@3GVIsZ^NB(s{7F$~3Vclq{tiPm-AwjozLKcwdrmcfvJqpU+g7`&60O>y;=| zhbKL)-GzJG!RyH27~AG;LpuRpSkJwG$|K*i$TmAV&8dUKyK_9Cf<;vP^kUXBQE!V!b?b%o}*l%Zy zqa&ivx|y}}?+&+{r2Za+N0S)u!-mK_h@f~0c4?e({F-Wc=J#mEGoP7KPzPyN_a8uu zt6YIEETtYh$&>XGeAl!XpYJLYQow(wG>Zn{(*UPp3C@GqXDmid3f}EAPH#H;m?i3d z18`jihH+n1GU@Cfv!85r?#kGe>+&5h(9Z8PgB`XzF7QO;J2>x^e@C++LwCD0L!_Vl z`g&O(a5ybRhQ_`W>e?w_y4Yb>oRd{(Q#D{JmRr?=16)7#{u+7QY{~2CHRBX-2ub*| zqW_}|2yX3tG{z@v&D^C~yh?Fjdn+Ep`1RKQvp*cd6WBaCHlD4lUn*melL2vI>hj8p z=#aaJ_xhZB$A2$12KTG9p(an4>dgn$SzpX8JmFVqzO0}*d zzY&XPD|20WGbV&CnyWnEtKYVr$l@0qi5d@#SS?N>Yo0nMSbHX#&EWZJwznPb>qN)l zb9P8~*T+}zN9(t$d3%2_-1(TsN{t;V{~~ObQoq}rR}Fx4`?9ACS`9A)E}9u~NITw@*D_YhKzT(9w8wC4^YioS!r5xd9H3gZ1vLiJzNDdY ziDr}JOwg?#J`YX*(t{4%T|6`ep09I$OjF;C>j;l9p@&L+=22eH{;TFHe;(MoB~00$ zE;uyDT8&XAzO^%)Ex2?&jpL@ES%GsxMkh_$l2j7ZTQyADH#f1=0*rQ)~y>8SDKfep$OcZH)-oYFC&}BI# z%#~>320@#S{B!mT9{Uf5O{x<4_~u^yOn&3CY?!=8auH4cexPqyaB&j-H)5r^WnLUZ zRQR&DrwXMSl98|fQ7MCXZ=0qnMLM*O6~S6wjeMGQaj__m3m_URNVEQ z!j}+`@Y<65sgRA*k@54&G1+5(ujacx8J`{#PjnA{Z7(FF;YF+letYb4=;UMULOWOE zK4~^)8hY5x_BX*Ycw%1#r<* z?GdhLhkr`1dCzZgf=_f_ZgG7@N?RY_de0+Mx!%HK!kVk1{;vWCZOz^b`F86YnIA*% zx#;Dc2E>iaw+SUq=x4WcDW+EAW=dsH&Ryu14%aQd@@{Rue7j>kiKDkGkqB32DBvR* z`HCLFrPt2(#6RYkbAS`;WP66N3T4K0bB0h_ASWc`Kj-<)s-!1K;2WEn3pqXT4}7nN zKEOO*&Nqv7^iKr~jlLFsessAP@!fqFm}BMe2DVj~9PLzH_Je$jE(H(5PrDanRz(g? zQ%;|~aaSx{-81U#RsE{{Zo?A`+(|s0&THG=kxQH^Agb;0v4I;Z8mHF35*M(SH@R$0 zwB>J|vD=ln;CpXnLf_}DvV=GH#D~d%YgQxW=)Jn*GKwI4Ns2vmaAC^JnC}fG3pYs} z?TdtskJY$EU-(v@?2&hQVx{F!8F`A#g#Ggh3OZLe_kLC?6sMZe8p;ZZ+0-Mg@Lo@g zjmWb!p4;BalU}KyC-4pj-IntAV~{^Dv@eFB$>km93EM9`UUyPozEg7$dS$OHlUNEk z@HRPTG9nQc>*W6=m@rBGgJM4Q<2PFSieBru6U*1=Lht=fZ=#s(;M?ad-z0Gi;Z2{< zZYz9aT#mSS9KSV4h|uRwXMb^Y*$FG_9Gp`gHhNSs*IHHytB`3;Y~kyjdO04hCm1cMA{lFmFV9_ z-a@tq5#6_oNiEAcO*Yj^V}TqUhP858fYtqy|(#+FHRk6ZHZd z`jv&)!s{+04x71@uKBTV#z!o~ZAc!e;7i#{?DWsg*J?l+=4+#487!XSts3@b8kb)m z;_-Q$!x{~ez3ZRzm|r)oKWjhe$zNjyil6J0ilMeaR23cviCf9=JO1!e7irEfAb54392 zi^+Yecp!iJ$gU>d>bG81%)wNF`*DyyWWFIdbq)^t7w`ba_0c^Ueoy?5PFRQBI}o$D z$+jT+(FhR^r&gQNg4%fSu*w67TC4On%M51JVHAk0;j4!howS3_`Ydu7MlOlRgv4P1 z-)|!WW8wDz$XQdxsx_|9ZGe?kP1#l_M$>TTds8|R_nH!QdWkq>J(|A zvc;H*RY8M04oAeRi$UL>KYVTce0zs_Dfw0X zPaScj-{OOffDP%bI@ko^0sy+(KcF(F3sZYPz%68O$K}XVT~PtzIESpoe816Y?+$L2 z6e6BAq}%mO_rv_Z2(>&;`@ns60PF1c^16NA0pii+KM{e1wyo$$VE162vS9GJ{9jT) z`Pju8W0%KATz@7q-GW2&!dX^6vRUX-~;z^a2vFSDxarEcKXj(P*Hrs}`=Kw`{qp1+;hp6k{*j$HWP4J!D~!+hV72SEi*2 z2}AeolK{fBX+3r7YJZ=9&k1X;KQlQ7HFd&D1s^SdO=jg=wu6o`HZyKN$&qmRb?Nm5 zo{!H9r0oo6%}R2pXP&&jRQtT_+!()e{_s$|gh+RK1^aN(`HkDVwzDyR`MIhN(>jES zg{<;l>TZ1YQ_-~_e^mKEGNEgrbryeOvt5(xYfT6R2V=a^1Fhaep^>3*-zxmc_v-Dy zg-}HFW_F;K`(l6lI#swO5F39t*jj$*A$YJETqV>ZZd35X%D%>%q38T5AaTK;K}Grv zrLdH**kr%=i2O3~*JlV$@=>KVOHbP*hcZ710tO$7w=3;;m zj7=_7%6E)D_tBMwDv27Lw4Wn+>c5sW;W`s|FW@`hX)7*)Io($E;b#_OCZ!`+-}b$SNLQ1_&u=ov|52rmUO!^v!kgg! zo5BFKQM7~`!pTjQ_4Xdh}=y6 zHn|KR)zypQy=@XG=`Tjq&Ykvu#I00a>__qkdfB-wfghu=kPUms*vq?ctIDX$micq2 zT$;v^UVyd9`(^*Ufx*ottVE?Mx~6~B6?ZJm*9r1W8zaC`QhddKx6s<37zPXwsHD8f`S@1Jz&pMqB1^%oD~ zGm&}8Wrs%nzWre7u$iC((^8qm%r4P|1K_^+jTgtFzy)jST3*QdJWxG3s1qp=aKM>O zOPvyjOg|2gP{cy0I{pY^7Wm|VcV7nwLT~d0UT?kK*QUpO!iA`Nw;OOo?LM29j;NcS z1Y&OIJ5uc$Gp_y2v41)wlE8KL14MJwYJ0G>wnJi>dnA(+<0tN5vmPzvTlcbxjmIsx zy3N!UyF90f4_0TtI!Su)-8Pf)l4l6xk7O%SnjS-6aE^tA(IRC-ieGB?zBTtm$O1#B zbtv}EIT!wPVR^T{K|Yz-D2FksOxa~AFQo$yp`^XBrHEQJZoo4uoJ3KQ`JMMcb$9v> z!ro(c$cq%?$3iOVaD`>GUWR5)jrpaITVer&yMqkuxAQF4ujbpJ*YqT|T)uD=zciWu z>kZ4!dX>fFv0+Dt+`65)2O&X1@ehM^nDsdI7PiN)i>v4~d76J{_&&-yZnwnzsGYo6 z98{(DS=nIRZ<*tgR@W;>-Jeoh-Qr1|tIYLUu0Qlb@nzQQU%X#|wUwz% z#oX_j?UV5j=JD#og|No~}TWwVptz8tgQngz|2Wl0yYDQ`8R!P+e z32oJ`8nw6DtM*RR-l;u`5X2rqNFtJNUhmKCcl-Tw{>*i5=bY=xbv^Ho$MaDTyBHty z`_i5_{j*7`%iT9w@V(qnjAid_{^5>>*0Uw4)~)Raz=*@gg8{U8MbuVF;cFew`@7=} z)jbKTN*Vc{TAVCmU?&<79cm6imH}3OQ+eqW$9+CarXjk*v!}; z1IjhiMf1}H^+3+gZiJh=P0=rZK}z0OpZI6yUtasC+RYN@f8znDX-aS^FlnkC54iVWt?(l42cC#vK;ZuQeyd6giEtm|UA(yJW?Ia?>WH?P?-@ zB_jLT%v$6a*8(+*cx0uM>H@0Pal1AYpwPi!m{nz#i>Av3*(NFa$WNCj)T!ckxYvA?PDq$wV6$H z*tpF9#=NItdzy;ls=;L^`US@U-b-(<@y$F0dK-y@&aDiRB|e;_ldfEGcec3ZxiuE9 z;RjF@;8VW#Z!=TLfHDM;9&6500gpRbpAWp!UJ`{Wv}hq0Z0S|O6AGDuiN_C$);F{2 zTVAgqs4;V2PIEZ-0EqST{pK^~e|r|n9!o#lf{!SxxSwq(VtC>$7L{cu zW}rtm@}X0Szd}GyUu-2|)U8qSwpFu{uz8kW^gFUh{-sR8k~c59N+cSBQiV3D^}0yw z{?wQbn(#_EM`JjOiepL5PA_6OlrWqlM+LD@(@%j_xB-bEe;g^nKmN zK-a5gkXrijjur*&IO$g!Cl%d%@0bNXi~5F(m|geOk%h?9@C4g0uj`+b{XFNQ@lX