From 586fe105253372416ca7b1629feedc6011ac81a2 Mon Sep 17 00:00:00 2001 From: Alan Ren Date: Fri, 17 May 2019 20:24:02 -0700 Subject: [PATCH] resource deployment ext implementation -wip (#5508) * resource types * implement the dialog * remove unused method * fix issues * formatting * 5-17 * address comments and more tests --- build/gulpfile.hygiene.js | 5 +- extensions/resource-deployment/.vscodeignore | 4 +- .../resource-deployment/coverageConfig.json | 17 + extensions/resource-deployment/images/aks.svg | 1 - .../resource-deployment/images/kubernetes.svg | 1 - extensions/resource-deployment/images/new.svg | 1 - .../images/new_inverse.svg | 1 - .../resource-deployment/images/sql_bdc.svg | 45 ++ .../images/sql_bdc_inverse.svg | 46 ++ .../resource-deployment/images/sql_server.svg | 1 + .../images/sql_server_inverse.svg | 1 + .../notebooks/sql2017-image-docker.ipynb | 72 ++ .../notebooks/sql2019-ctp25-bdc-aks.ipynb | 29 + .../sql2019-ctp25-bdc-existing-cluster.ipynb | 29 + .../notebooks/sql2019-ctp30-bdc-aks.ipynb | 29 + .../sql2019-ctp30-bdc-existing-cluster.ipynb | 29 + .../notebooks/sql2019-image-docker.ipynb | 72 ++ extensions/resource-deployment/package.json | 156 +++++ .../resource-deployment/package.nls.json | 24 +- .../resource-deployment/src/interfaces.ts | 74 +++ extensions/resource-deployment/src/main.ts | 38 +- .../src/services/notebookService.ts | 79 +++ .../src/services/platformService.ts | 41 ++ .../src/services/resourceTypeService.ts | 174 +++++ .../src/services/tools/azCLITool.ts | 43 ++ .../src/services/tools/dockerTool.ts | 44 ++ .../src/services/tools/kubectlTool.ts | 43 ++ .../src/services/tools/mssqlctlTool.ts | 52 ++ .../src/services/tools/pythonTool.ts | 44 ++ .../src/services/toolsService.ts | 52 ++ .../resource-deployment/src/test/index.ts | 30 + .../src/test/notebookService.test.ts | 130 ++++ .../src/ui/resourceDeploymentDialog.ts | 198 +++++- extensions/resource-deployment/yarn.lock | 622 ++++++++++++++++++ scripts/test-extensions-unit.bat | 1 + scripts/test-extensions-unit.sh | 1 + 36 files changed, 2208 insertions(+), 21 deletions(-) create mode 100644 extensions/resource-deployment/coverageConfig.json delete mode 100644 extensions/resource-deployment/images/aks.svg delete mode 100644 extensions/resource-deployment/images/kubernetes.svg delete mode 100644 extensions/resource-deployment/images/new.svg delete mode 100644 extensions/resource-deployment/images/new_inverse.svg create mode 100644 extensions/resource-deployment/images/sql_bdc.svg create mode 100644 extensions/resource-deployment/images/sql_bdc_inverse.svg create mode 100644 extensions/resource-deployment/images/sql_server.svg create mode 100644 extensions/resource-deployment/images/sql_server_inverse.svg create mode 100644 extensions/resource-deployment/notebooks/sql2017-image-docker.ipynb create mode 100644 extensions/resource-deployment/notebooks/sql2019-ctp25-bdc-aks.ipynb create mode 100644 extensions/resource-deployment/notebooks/sql2019-ctp25-bdc-existing-cluster.ipynb create mode 100644 extensions/resource-deployment/notebooks/sql2019-ctp30-bdc-aks.ipynb create mode 100644 extensions/resource-deployment/notebooks/sql2019-ctp30-bdc-existing-cluster.ipynb create mode 100644 extensions/resource-deployment/notebooks/sql2019-image-docker.ipynb create mode 100644 extensions/resource-deployment/src/services/notebookService.ts create mode 100644 extensions/resource-deployment/src/services/platformService.ts create mode 100644 extensions/resource-deployment/src/services/resourceTypeService.ts create mode 100644 extensions/resource-deployment/src/services/tools/azCLITool.ts create mode 100644 extensions/resource-deployment/src/services/tools/dockerTool.ts create mode 100644 extensions/resource-deployment/src/services/tools/kubectlTool.ts create mode 100644 extensions/resource-deployment/src/services/tools/mssqlctlTool.ts create mode 100644 extensions/resource-deployment/src/services/tools/pythonTool.ts create mode 100644 extensions/resource-deployment/src/services/toolsService.ts create mode 100644 extensions/resource-deployment/src/test/index.ts create mode 100644 extensions/resource-deployment/src/test/notebookService.test.ts diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index ac633fea72..c2c1893ee2 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -95,7 +95,8 @@ const indentationFilter = [ '!**/*.{xlf,docx,sql,vsix}', '!extensions/mssql/sqltoolsservice/**', '!extensions/import/flatfileimportservice/**', - '!extensions/admin-tool-ext-win/ssmsmin/**' + '!extensions/admin-tool-ext-win/ssmsmin/**', + '!extensions/resource-deployment/notebooks/**' ]; const copyrightFilter = [ @@ -257,7 +258,7 @@ function hygiene(some) { const lines = file.__lines; // Only take the first 10 lines to reduce false positives- the compiler will throw an error if it's not the first non-comment line in a file // (10 is used to account for copyright and extraneous newlines) - lines.slice(0,10).forEach((line, i) => { + lines.slice(0, 10).forEach((line, i) => { if (/\s*'use\s*strict\s*'/.test(line)) { console.error(file.relative + '(' + (i + 1) + ',1): Unnecessary \'use strict\' - this is already added by the compiler'); errorCount++; diff --git a/extensions/resource-deployment/.vscodeignore b/extensions/resource-deployment/.vscodeignore index 7f47d852aa..1142d2b348 100644 --- a/extensions/resource-deployment/.vscodeignore +++ b/extensions/resource-deployment/.vscodeignore @@ -1,2 +1,4 @@ +coverage/** +out/test/** src/** -tsconfig.json +coverageConfig.json \ No newline at end of file diff --git a/extensions/resource-deployment/coverageConfig.json b/extensions/resource-deployment/coverageConfig.json new file mode 100644 index 0000000000..9b87f7e033 --- /dev/null +++ b/extensions/resource-deployment/coverageConfig.json @@ -0,0 +1,17 @@ +{ + "enabled": true, + "relativeSourcePath": "..", + "relativeCoverageDir": "../../coverage", + "ignorePatterns": [ + "**/node_modules/**" + ], + "includePid": false, + "reports": [ + "cobertura" + ], + "verbose": false, + "remapOptions": { + "basePath": ".", + "useAbsolutePaths": true + } +} \ No newline at end of file diff --git a/extensions/resource-deployment/images/aks.svg b/extensions/resource-deployment/images/aks.svg deleted file mode 100644 index 1df4bc8835..0000000000 --- a/extensions/resource-deployment/images/aks.svg +++ /dev/null @@ -1 +0,0 @@ -AKS \ No newline at end of file diff --git a/extensions/resource-deployment/images/kubernetes.svg b/extensions/resource-deployment/images/kubernetes.svg deleted file mode 100644 index 0b17a4013a..0000000000 --- a/extensions/resource-deployment/images/kubernetes.svg +++ /dev/null @@ -1 +0,0 @@ -Kubernetes \ No newline at end of file diff --git a/extensions/resource-deployment/images/new.svg b/extensions/resource-deployment/images/new.svg deleted file mode 100644 index f76813b363..0000000000 --- a/extensions/resource-deployment/images/new.svg +++ /dev/null @@ -1 +0,0 @@ -new_16x16 \ No newline at end of file diff --git a/extensions/resource-deployment/images/new_inverse.svg b/extensions/resource-deployment/images/new_inverse.svg deleted file mode 100644 index 787edf63bd..0000000000 --- a/extensions/resource-deployment/images/new_inverse.svg +++ /dev/null @@ -1 +0,0 @@ -new_inverse_16x16 \ No newline at end of file diff --git a/extensions/resource-deployment/images/sql_bdc.svg b/extensions/resource-deployment/images/sql_bdc.svg new file mode 100644 index 0000000000..fcf1133186 --- /dev/null +++ b/extensions/resource-deployment/images/sql_bdc.svg @@ -0,0 +1,45 @@ + + + + + + image/svg+xml + + sql_bigdata_cluster + + + + + + + sql_bigdata_cluster + + + + diff --git a/extensions/resource-deployment/images/sql_bdc_inverse.svg b/extensions/resource-deployment/images/sql_bdc_inverse.svg new file mode 100644 index 0000000000..dbc823e9f0 --- /dev/null +++ b/extensions/resource-deployment/images/sql_bdc_inverse.svg @@ -0,0 +1,46 @@ + + + + + + image/svg+xml + + sql_bigdata_cluster + + + + + + + sql_bigdata_cluster + + + + diff --git a/extensions/resource-deployment/images/sql_server.svg b/extensions/resource-deployment/images/sql_server.svg new file mode 100644 index 0000000000..03cff4d932 --- /dev/null +++ b/extensions/resource-deployment/images/sql_server.svg @@ -0,0 +1 @@ +sql_server \ No newline at end of file diff --git a/extensions/resource-deployment/images/sql_server_inverse.svg b/extensions/resource-deployment/images/sql_server_inverse.svg new file mode 100644 index 0000000000..1ce3f48b39 --- /dev/null +++ b/extensions/resource-deployment/images/sql_server_inverse.svg @@ -0,0 +1 @@ +sql_server_inverse \ No newline at end of file diff --git a/extensions/resource-deployment/notebooks/sql2017-image-docker.ipynb b/extensions/resource-deployment/notebooks/sql2017-image-docker.ipynb new file mode 100644 index 0000000000..6a915e14ef --- /dev/null +++ b/extensions/resource-deployment/notebooks/sql2017-image-docker.ipynb @@ -0,0 +1,72 @@ +{ + "metadata": { + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python", + "version": "3.6.6", + "mimetype": "text/x-python", + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "pygments_lexer": "ipython3", + "nbconvert_exporter": "python", + "file_extension": ".py" + } + }, + "nbformat_minor": 2, + "nbformat": 4, + "cells": [ + { + "cell_type": "markdown", + "source": "First, pull the Microsoft SQL Server 2017 Image", + "metadata": {} + }, + { + "cell_type": "code", + "source": "cmd = f'docker pull mcr.microsoft.com/mssql/server:2017-latest'\r\n!{cmd}", + "metadata": {}, + "outputs": [], + "execution_count": 49 + }, + { + "cell_type": "markdown", + "source": "List all the containers", + "metadata": {} + }, + { + "cell_type": "code", + "source": "cmd = f'docker ps -a'\r\n!{cmd}", + "metadata": {}, + "outputs": [], + "execution_count": 50 + }, + { + "cell_type": "markdown", + "source": "Start a new container with SQL Server 2017", + "metadata": {} + }, + { + "cell_type": "code", + "source": "import time\r\n\r\ncontainer_name = 'sql2017-' + time.strftime(\"%Y%m%d%H%M%S\", time.localtime())\r\nprint('New container name: ' + container_name)\r\n\r\nsql_port = 1433\r\nsa_password = \"\"\r\n\r\ncmd = f'docker run -e ACCEPT_EULA=Y -e \"SA_PASSWORD={sa_password}\" -p {sql_port}:{sql_port} --name {container_name} -d mcr.microsoft.com/mssql/server:2017-latest'\r\nprint(cmd)\r\n!{cmd}", + "metadata": {}, + "outputs": [], + "execution_count": 51 + }, + { + "cell_type": "markdown", + "source": "List all the containers", + "metadata": {} + }, + { + "cell_type": "code", + "source": "cmd = f'docker ps -a'\r\n!{cmd}", + "metadata": {}, + "outputs": [], + "execution_count": 48 + } + ] +} \ No newline at end of file diff --git a/extensions/resource-deployment/notebooks/sql2019-ctp25-bdc-aks.ipynb b/extensions/resource-deployment/notebooks/sql2019-ctp25-bdc-aks.ipynb new file mode 100644 index 0000000000..5ae0b48f5c --- /dev/null +++ b/extensions/resource-deployment/notebooks/sql2019-ctp25-bdc-aks.ipynb @@ -0,0 +1,29 @@ +{ + "metadata": { + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python", + "version": "3.6.6", + "mimetype": "text/x-python", + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "pygments_lexer": "ipython3", + "nbconvert_exporter": "python", + "file_extension": ".py" + } + }, + "nbformat_minor": 2, + "nbformat": 4, + "cells": [ + { + "cell_type": "markdown", + "source": "TODO", + "metadata": {} + } + ] +} \ No newline at end of file diff --git a/extensions/resource-deployment/notebooks/sql2019-ctp25-bdc-existing-cluster.ipynb b/extensions/resource-deployment/notebooks/sql2019-ctp25-bdc-existing-cluster.ipynb new file mode 100644 index 0000000000..5ae0b48f5c --- /dev/null +++ b/extensions/resource-deployment/notebooks/sql2019-ctp25-bdc-existing-cluster.ipynb @@ -0,0 +1,29 @@ +{ + "metadata": { + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python", + "version": "3.6.6", + "mimetype": "text/x-python", + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "pygments_lexer": "ipython3", + "nbconvert_exporter": "python", + "file_extension": ".py" + } + }, + "nbformat_minor": 2, + "nbformat": 4, + "cells": [ + { + "cell_type": "markdown", + "source": "TODO", + "metadata": {} + } + ] +} \ No newline at end of file diff --git a/extensions/resource-deployment/notebooks/sql2019-ctp30-bdc-aks.ipynb b/extensions/resource-deployment/notebooks/sql2019-ctp30-bdc-aks.ipynb new file mode 100644 index 0000000000..5ae0b48f5c --- /dev/null +++ b/extensions/resource-deployment/notebooks/sql2019-ctp30-bdc-aks.ipynb @@ -0,0 +1,29 @@ +{ + "metadata": { + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python", + "version": "3.6.6", + "mimetype": "text/x-python", + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "pygments_lexer": "ipython3", + "nbconvert_exporter": "python", + "file_extension": ".py" + } + }, + "nbformat_minor": 2, + "nbformat": 4, + "cells": [ + { + "cell_type": "markdown", + "source": "TODO", + "metadata": {} + } + ] +} \ No newline at end of file diff --git a/extensions/resource-deployment/notebooks/sql2019-ctp30-bdc-existing-cluster.ipynb b/extensions/resource-deployment/notebooks/sql2019-ctp30-bdc-existing-cluster.ipynb new file mode 100644 index 0000000000..5ae0b48f5c --- /dev/null +++ b/extensions/resource-deployment/notebooks/sql2019-ctp30-bdc-existing-cluster.ipynb @@ -0,0 +1,29 @@ +{ + "metadata": { + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python", + "version": "3.6.6", + "mimetype": "text/x-python", + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "pygments_lexer": "ipython3", + "nbconvert_exporter": "python", + "file_extension": ".py" + } + }, + "nbformat_minor": 2, + "nbformat": 4, + "cells": [ + { + "cell_type": "markdown", + "source": "TODO", + "metadata": {} + } + ] +} \ No newline at end of file diff --git a/extensions/resource-deployment/notebooks/sql2019-image-docker.ipynb b/extensions/resource-deployment/notebooks/sql2019-image-docker.ipynb new file mode 100644 index 0000000000..b7cbf977e6 --- /dev/null +++ b/extensions/resource-deployment/notebooks/sql2019-image-docker.ipynb @@ -0,0 +1,72 @@ +{ + "metadata": { + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python", + "version": "3.6.6", + "mimetype": "text/x-python", + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "pygments_lexer": "ipython3", + "nbconvert_exporter": "python", + "file_extension": ".py" + } + }, + "nbformat_minor": 2, + "nbformat": 4, + "cells": [ + { + "cell_type": "markdown", + "source": "First, pull the Microsoft SQL Server 2019 Image", + "metadata": {} + }, + { + "cell_type": "code", + "source": "cmd = f'docker pull mcr.microsoft.com/mssql/server:2019-latest'\r\n!{cmd}", + "metadata": {}, + "outputs": [], + "execution_count": 49 + }, + { + "cell_type": "markdown", + "source": "List all the containers", + "metadata": {} + }, + { + "cell_type": "code", + "source": "cmd = f'docker ps -a'\r\n!{cmd}", + "metadata": {}, + "outputs": [], + "execution_count": 50 + }, + { + "cell_type": "markdown", + "source": "Start a new container with SQL Server 2019", + "metadata": {} + }, + { + "cell_type": "code", + "source": "import time\r\n\r\ncontainer_name = 'sql2019-' + time.strftime(\"%Y%m%d%H%M%S\", time.localtime())\r\nprint('New container name: ' + container_name)\r\n\r\nsql_port = 1433\r\nsa_password = \"\"\r\n\r\ncmd = f'docker run -e ACCEPT_EULA=Y -e \"SA_PASSWORD={sa_password}\" -p {sql_port}:{sql_port} --name {container_name} -d mcr.microsoft.com/mssql/server:2019-latest'\r\nprint(cmd)\r\n!{cmd}", + "metadata": {}, + "outputs": [], + "execution_count": 51 + }, + { + "cell_type": "markdown", + "source": "List all the containers", + "metadata": {} + }, + { + "cell_type": "code", + "source": "cmd = f'docker ps -a'\r\n!{cmd}", + "metadata": {}, + "outputs": [], + "execution_count": 48 + } + ] +} \ No newline at end of file diff --git a/extensions/resource-deployment/package.json b/extensions/resource-deployment/package.json index 8d68d81e62..61a4c555fd 100644 --- a/extensions/resource-deployment/package.json +++ b/extensions/resource-deployment/package.json @@ -50,7 +50,163 @@ ] } }, + "resourceTypes": [ + { + "name": "sql-image", + "displayName": "%resource-type-sql-image-display-name%", + "description": "%resource-type-sql-image-description%", + "platforms": [ + "darwin", + "win32", + "linux" + ], + "icon": { + "light": "./images/sql_server.svg", + "dark": "./images/sql_server_inverse.svg" + }, + "options": [ + { + "name": "version", + "displayName": "%version-display-name%", + "values": [ + { + "name": "sql2017", + "displayName": "%sql-2017-display-name%" + }, + { + "name": "sql2019", + "displayName": "%sql-2019-display-name%" + } + ] + } + ], + "providers": [ + { + "notebook": "%sql-2017-docker-notebook%", + "requiredTools": [ + { + "name": "docker" + } + ], + "when": "version=sql2017" + }, + { + "notebook": "%sql-2019-docker-notebook%", + "requiredTools": [ + { + "name": "docker" + } + ], + "when": "version=sql2019" + } + ] + }, + { + "name": "sql-bdc", + "displayName": "%resource-type-sql-bdc-display-name%", + "description": "%resource-type-sql-bdc-description%", + "platforms": [ + "darwin", + "linux", + "win32" + ], + "icon": { + "light": "./images/sql_bdc.svg", + "dark": "./images/sql_bdc_inverse.svg" + }, + "options": [ + { + "name": "version", + "displayName": "%version-display-name%", + "values": [ + { + "name": "ctp2.5", + "displayName": "%sql-2019ctp25-display-name%" + }, + { + "name": "ctp3.0", + "displayName": "%sql-2019ctp30-display-name%" + } + ] + }, + { + "name": "target", + "displayName": "%bdc-deployment-target%", + "values": [ + { + "name": "aks", + "displayName": "%bdc-deployment-target-aks%" + }, + { + "name": "existingCluster", + "displayName": "%bdc-deployment-target-existing-cluster%" + } + ] + } + ], + "providers": [ + { + "notebook": "%bdc-ctp25-aks-notebook%", + "requiredTools": [ + { + "name": "kubectl" + }, + { + "name": "azcli" + }, + { + "name": "mssqlctl" + } + ], + "when": "target=aks&&version=ctp2.5" + }, + { + "notebook": "%bdc-ctp25-existing-cluster-notebook%", + "requiredTools": [ + { + "name": "kubectl" + }, + { + "name": "mssqlctl" + } + ], + "when": "target=existingCluster&&version=ctp2.5" + }, + { + "notebook": "%bdc-ctp30-aks-notebook%", + "requiredTools": [ + { + "name": "kubectl" + }, + { + "name": "azcli" + }, + { + "name": "mssqlctl" + } + ], + "when": "target=aks&&version=ctp3.0" + }, + { + "notebook": "%bdc-ctp30-existing-cluster-notebook%", + "requiredTools": [ + { + "name": "kubectl" + }, + { + "name": "mssqlctl" + } + ], + "when": "target=existingCluster&&version=ctp3.0" + } + ] + } + ], "dependencies": { "vscode-nls": "^3.2.1" + }, + "devDependencies": { + "typemoq": "^2.1.0", + "vscode": "^1.1.26" } } \ No newline at end of file diff --git a/extensions/resource-deployment/package.nls.json b/extensions/resource-deployment/package.nls.json index 46d7e7bcbb..8bdd24ffd3 100644 --- a/extensions/resource-deployment/package.nls.json +++ b/extensions/resource-deployment/package.nls.json @@ -1,7 +1,25 @@ { - "extension-displayName":"Resource Deployment extension for Azure Data Studio", - "extension-description":"Provides a notebook-based experience to deploy SQL Server and other Azure Data Services", + "extension-displayName": "Resource Deployment extension for Azure Data Studio", + "extension-description": "Provides a notebook-based experience to deploy SQL Server and other Azure Data Services", "deploy-sql-image-command-name": "Deploy SQL Server on Docker…", "deploy-sql-bdc-command-name": "Deploy SQL Server Big Data Cluster…", - "deploy-resource-command-category": "Resource Deployment" + "deploy-resource-command-category": "Resource Deployment", + "resource-type-sql-image-display-name": "Container Image", + "resource-type-sql-image-description": "SQL Server container image on Docker", + "resource-type-sql-bdc-display-name": "Big Data Cluster", + "resource-type-sql-bdc-description": "SQL Server Big Data Cluster", + "version-display-name": "Version", + "sql-2017-display-name": "SQL Server 2017", + "sql-2019-display-name": "SQL Server 2019", + "sql-2017-docker-notebook": "./notebooks/sql2017-image-docker.ipynb", + "sql-2019-docker-notebook": "./notebooks/sql2019-image-docker.ipynb", + "sql-2019ctp25-display-name": "SQL Server 2019 Big Data Cluster CTP 2.5", + "sql-2019ctp30-display-name": "SQL Server 2019 Big Data Cluster CTP 3.0", + "bdc-deployment-target": "Deployment target", + "bdc-deployment-target-aks": "New Azure Kubernetes Service Cluster", + "bdc-deployment-target-existing-cluster": "Existing Kubernetes Cluster", + "bdc-ctp25-aks-notebook": "./notebooks/sql2019-ctp25-bdc-aks.ipynb", + "bdc-ctp25-existing-cluster-notebook": "./notebooks/sql2019-ctp25-bdc-existing-cluster.ipynb", + "bdc-ctp30-aks-notebook": "./notebooks/sql2019-ctp30-bdc-aks.ipynb", + "bdc-ctp30-existing-cluster-notebook": "./notebooks/sql2019-ctp30-bdc-existing-cluster.ipynb" } \ No newline at end of file diff --git a/extensions/resource-deployment/src/interfaces.ts b/extensions/resource-deployment/src/interfaces.ts index b7dec93b31..ad2f3eb536 100644 --- a/extensions/resource-deployment/src/interfaces.ts +++ b/extensions/resource-deployment/src/interfaces.ts @@ -3,3 +3,77 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ 'use strict'; + +export interface ResourceType { + name: string; + displayName: string; + description: string; + platforms: string[]; + icon: { light: string; dark: string }; + options: ResourceTypeOption[]; + providers: DeploymentProvider[]; + getProvider(selectedOptions: { option: string, value: string }[]): DeploymentProvider | undefined; +} + +export interface ResourceTypeOption { + name: string; + displayName: string; + values: ResourceTypeOptionValue[]; +} + +export interface ResourceTypeOptionValue { + name: string; + displayName: string; +} + +export interface DeploymentProvider { + notebook: string | NotebookInfo; + requiredTools: ToolRequirementInfo[]; + when: string; +} + +export interface NotebookInfo { + win32: string; + darwin: string; + linux: string; +} + +export interface ToolRequirementInfo { + name: string; + version: string; +} + +export enum ToolType { + Unknown, + AzCli, + KubeCtl, + Docker, + Python, + MSSQLCtl +} + +export interface ToolStatusInfo { + type: ToolType; + name: string; + description: string; + version: string; + status: ToolInstallationStatus; +} + +export interface ITool { + readonly name: string; + readonly displayName: string; + readonly description: string; + readonly type: ToolType; + readonly supportAutoInstall: boolean; + + getInstallationStatus(versionExpression: string): Thenable; + install(version: string): Thenable; +} + +export enum ToolInstallationStatus { + NotInstalled, + Installed, + Installing, + FailedToInstall +} \ No newline at end of file diff --git a/extensions/resource-deployment/src/main.ts b/extensions/resource-deployment/src/main.ts index 97f4fe99e2..2e0fe06c91 100644 --- a/extensions/resource-deployment/src/main.ts +++ b/extensions/resource-deployment/src/main.ts @@ -6,15 +6,45 @@ import vscode = require('vscode'); import { ResourceDeploymentDialog } from './ui/resourceDeploymentDialog'; +import { ToolsService } from './services/toolsService'; +import { NotebookService } from './services/notebookService'; +import { ResourceTypeService } from './services/resourceTypeService'; +import { PlatformService } from './services/platformService'; +import * as nls from 'vscode-nls'; + +const localize = nls.loadMessageBundle(); export function activate(context: vscode.ExtensionContext) { + const platformService = new PlatformService(); + const toolsService = new ToolsService(); + const notebookService = new NotebookService(platformService); + const resourceTypeService = new ResourceTypeService(platformService, toolsService); + + const resourceTypes = resourceTypeService.getResourceTypes(); + const validationFailures = resourceTypeService.validateResourceTypes(resourceTypes); + if (validationFailures.length !== 0) { + const errorMessage = localize('resourceDeployment.FailedToLoadExtension', 'Failed to load extension: {0}, Error detected in the resource type definition in package.json, check debug console for details.', context.extensionPath); + vscode.window.showErrorMessage(errorMessage); + validationFailures.forEach(message => console.error(message)); + return; + } + + const openDialog = (resourceTypeName: string) => { + const filtered = resourceTypes.filter(resourceType => resourceType.name === resourceTypeName); + if (filtered.length !== 1) { + vscode.window.showErrorMessage(localize('resourceDeployment.UnknownResourceType', 'The resource type: {0} is not defined', resourceTypeName)); + } + else { + let dialog = new ResourceDeploymentDialog(context, notebookService, toolsService, resourceTypeService, filtered[0]); + dialog.open(); + } + }; + vscode.commands.registerCommand('azdata.resource.sql-image.deploy', () => { - let dialog = new ResourceDeploymentDialog(); - dialog.open(); + openDialog('sql-image'); }); vscode.commands.registerCommand('azdata.resource.sql-bdc.deploy', () => { - let dialog = new ResourceDeploymentDialog(); - dialog.open(); + openDialog('sql-bdc'); }); } diff --git a/extensions/resource-deployment/src/services/notebookService.ts b/extensions/resource-deployment/src/services/notebookService.ts new file mode 100644 index 0000000000..90542556be --- /dev/null +++ b/extensions/resource-deployment/src/services/notebookService.ts @@ -0,0 +1,79 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { NotebookInfo } from '../interfaces'; +import { isString } from 'util'; +import * as os from 'os'; +import * as path from 'path'; +import * as nls from 'vscode-nls'; +import { IPlatformService } from './platformService'; +const localize = nls.loadMessageBundle(); + +export interface INotebookService { + launchNotebook(notebook: string | NotebookInfo): void; +} + +export class NotebookService implements INotebookService { + + constructor(private platformService: IPlatformService) { } + + /** + * Copy the notebook to the user's home directory and launch the notebook from there. + * @param notebook the path of the notebook + */ + launchNotebook(notebook: string | NotebookInfo): void { + const notebookRelativePath = this.getNotebook(notebook); + const notebookFullPath = path.join(__dirname, '../../', notebookRelativePath); + if (notebookRelativePath && this.platformService.fileExists(notebookFullPath)) { + const targetFileName = this.getTargetNotebookFileName(notebookFullPath, os.homedir()); + this.platformService.copyFile(notebookFullPath, targetFileName); + this.platformService.openFile(targetFileName); + } + else { + this.platformService.showErrorMessage(localize('resourceDeployment.notebookNotFound', 'The notebook {0} does not exist', notebookFullPath)); + } + } + + /** + * get the notebook path for current platform + * @param notebook the notebook path + */ + getNotebook(notebook: string | NotebookInfo): string { + let notebookPath; + if (notebook && !isString(notebook)) { + const platform = this.platformService.platform(); + if (platform === 'win32') { + notebookPath = notebook.win32; + } else if (platform === 'darwin') { + notebookPath = notebook.darwin; + } else { + notebookPath = notebook.linux; + } + } else { + notebookPath = notebook; + } + return notebookPath; + } + + /** + * Get a file name that is not already used in the target directory + * @param notebook source notebook file name + * @param targetDirectory target directory + */ + getTargetNotebookFileName(notebook: string, targetDirectory: string): string { + const notebookFileExtension = '.ipynb'; + const baseName = path.basename(notebook, notebookFileExtension); + let targetFileName; + let idx = 0; + do { + const suffix = idx === 0 ? '' : `-${idx}`; + targetFileName = path.join(targetDirectory, `${baseName}${suffix}${notebookFileExtension}`); + idx++; + } while (this.platformService.fileExists(targetFileName)); + + return targetFileName; + } +} \ No newline at end of file diff --git a/extensions/resource-deployment/src/services/platformService.ts b/extensions/resource-deployment/src/services/platformService.ts new file mode 100644 index 0000000000..5f442df282 --- /dev/null +++ b/extensions/resource-deployment/src/services/platformService.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as fs from 'fs'; +import * as vscode from 'vscode'; + +/** + * Abstract of platform dependencies + */ +export interface IPlatformService { + platform(): string; + copyFile(source: string, target: string): void; + fileExists(file: string): boolean; + openFile(filePath: string): void; + showErrorMessage(message: string): void; +} + +export class PlatformService implements IPlatformService { + platform(): string { + return process.platform; + } + + copyFile(source: string, target: string): void { + fs.copyFileSync(source, target); + } + + fileExists(file: string): boolean { + return fs.existsSync(file); + } + + openFile(filePath: string): void { + vscode.commands.executeCommand('vscode.open', vscode.Uri.file(filePath)); + } + + showErrorMessage(message: string): void { + vscode.window.showErrorMessage(message); + } +} \ No newline at end of file diff --git a/extensions/resource-deployment/src/services/resourceTypeService.ts b/extensions/resource-deployment/src/services/resourceTypeService.ts new file mode 100644 index 0000000000..038e3ee18b --- /dev/null +++ b/extensions/resource-deployment/src/services/resourceTypeService.ts @@ -0,0 +1,174 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { ResourceType, ResourceTypeOption, DeploymentProvider } from '../interfaces'; +import { IToolsService } from './toolsService'; +import * as vscode from 'vscode'; +import { IPlatformService } from './platformService'; + +export interface IResourceTypeService { + getResourceTypes(filterByPlatform?: boolean): ResourceType[]; + validateResourceTypes(resourceTypes: ResourceType[]): string[]; +} + +export class ResourceTypeService implements IResourceTypeService { + private _resourceTypes: ResourceType[] = []; + + constructor(private platformService: IPlatformService, private toolsService: IToolsService) { } + + /** + * Get the supported resource types + * @param filterByPlatform indicates whether to return the resource types supported on current platform. + */ + getResourceTypes(filterByPlatform: boolean = true): ResourceType[] { + if (this._resourceTypes.length === 0) { + // If we load package.json directly using require(path) the contents won't be localized + this._resourceTypes = vscode.extensions.getExtension('microsoft.resource-deployment')!.packageJSON.resourceTypes as ResourceType[]; + this._resourceTypes.forEach(resourceType => { + resourceType.getProvider = (selectedOptions) => { return this.getProvider(resourceType, selectedOptions); }; + }); + } + + let resourceTypes = this._resourceTypes; + if (filterByPlatform) { + resourceTypes = resourceTypes.filter(resourceType => resourceType.platforms.includes(this.platformService.platform())); + } + + return resourceTypes; + } + + /** + * Validate the resource types and returns validation error messages if any. + * @param resourceTypes resource types to be validated + */ + validateResourceTypes(resourceTypes: ResourceType[]): string[] { + // NOTE: The validation error messages do not need to be localized as it is only meant for the developer's use. + const errorMessages: string[] = []; + if (!resourceTypes || resourceTypes.length === 0) { + errorMessages.push('Resource type list is empty'); + } else { + let resourceTypeIndex = 1; + resourceTypes.forEach(resourceType => { + this.validateResourceType(resourceType, `resource type index: ${resourceTypeIndex}`, errorMessages); + resourceTypeIndex++; + }); + } + + return errorMessages; + } + + private validateResourceType(resourceType: ResourceType, positionInfo: string, errorMessages: string[]): void { + this.validateNameDisplayName(resourceType, 'resource type', positionInfo, errorMessages); + if (!resourceType.icon || !resourceType.icon.dark || !resourceType.icon.light) { + errorMessages.push(`Icon for resource type is not specified properly. ${positionInfo} `); + } + + if (resourceType.options && resourceType.options.length > 0) { + let optionIndex = 1; + resourceType.options.forEach(option => { + const optionInfo = `${positionInfo}, option index: ${optionIndex} `; + this.validateResourceTypeOption(option, optionInfo, errorMessages); + optionIndex++; + }); + } + + this.validateProviders(resourceType, positionInfo, errorMessages); + } + + private validateResourceTypeOption(option: ResourceTypeOption, positionInfo: string, errorMessages: string[]): void { + this.validateNameDisplayName(option, 'option', positionInfo, errorMessages); + if (!option.values || option.values.length === 0) { + errorMessages.push(`Option contains no values.${positionInfo} `); + } else { + let optionValueIndex = 1; + option.values.forEach(optionValue => { + const optionValueInfo = `${positionInfo}, option value index: ${optionValueIndex} `; + this.validateNameDisplayName(optionValue, 'option value', optionValueInfo, errorMessages); + optionValueIndex++; + }); + + // Make sure the values are unique + for (let i = 0; i < option.values.length; i++) { + if (option.values[i].name && option.values[i].displayName) { + let dupePositions = []; + for (let j = i + 1; j < option.values.length; j++) { + if (option.values[i].name === option.values[j].name + || option.values[i].displayName === option.values[j].displayName) { + // +1 to make the position 1 based. + dupePositions.push(j + 1); + } + } + + if (dupePositions.length !== 0) { + errorMessages.push(`Option values with same name or display name are found at the following positions: ${i + 1}, ${dupePositions.join(',')}.${positionInfo} `); + } + } + } + } + } + + private validateProviders(resourceType: ResourceType, positionInfo: string, errorMessages: string[]): void { + if (!resourceType.providers || resourceType.providers.length === 0) { + errorMessages.push(`No providers defined for resource type, ${positionInfo}`); + } else { + let providerIndex = 1; + resourceType.providers.forEach(provider => { + const providerPositionInfo = `${positionInfo}, provider index: ${providerIndex} `; + if (!provider.notebook) { + errorMessages.push(`Notebook is not specified for the provider, ${providerPositionInfo}`); + } + + if (provider.requiredTools && provider.requiredTools.length > 0) { + provider.requiredTools.forEach(tool => { + if (!this.toolsService.getToolByName(tool.name)) { + errorMessages.push(`The tool is not supported: ${tool.name}, ${providerPositionInfo} `); + } + }); + } + providerIndex++; + }); + } + } + + private validateNameDisplayName(obj: { name: string; displayName: string }, type: string, positionInfo: string, errorMessages: string[]): void { + if (!obj.name) { + errorMessages.push(`Name of the ${type} is empty.${positionInfo} `); + } + if (!obj.displayName) { + errorMessages.push(`Display name of the ${type} is empty.${positionInfo} `); + } + } + + /** + * Get the provider based on the selected options + */ + private getProvider(resourceType: ResourceType, selectedOptions: { option: string, value: string }[]): DeploymentProvider | undefined { + for (let i = 0; i < resourceType.providers.length; i++) { + const provider = resourceType.providers[i]; + + const expected = provider.when.replace(' ', '').split('&&').sort(); + let actual: string[] = []; + selectedOptions.forEach(option => { + actual.push(`${option.option}=${option.value}`); + }); + actual = actual.sort(); + + if (actual.length === expected.length) { + let matches = true; + for (let j = 0; j < actual.length; j++) { + if (actual[j] !== expected[j]) { + matches = false; + break; + } + } + if (matches) { + return provider; + } + } + } + return undefined; + } +} diff --git a/extensions/resource-deployment/src/services/tools/azCLITool.ts b/extensions/resource-deployment/src/services/tools/azCLITool.ts new file mode 100644 index 0000000000..99cc4383c3 --- /dev/null +++ b/extensions/resource-deployment/src/services/tools/azCLITool.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; +import { ToolType, ITool, ToolInstallationStatus } from '../../interfaces'; +import * as nls from 'vscode-nls'; +const localize = nls.loadMessageBundle(); + +export class AzCliTool implements ITool { + get name(): string { + return 'azcli'; + } + + get description(): string { + return localize('resourceDeployment.AzCLIDescription', 'Tool used for managing Azure services'); + } + + get type(): ToolType { + return ToolType.AzCli; + } + + get displayName(): string { + return localize('resourceDeployment.AzCLIDisplayName', 'Azure CLI'); + } + + get supportAutoInstall(): boolean { + return true; + } + + install(version: string): Thenable { + throw new Error('Method not implemented.'); + } + + getInstallationStatus(versionExpression: string): Thenable { + let promise = new Promise(resolve => { + setTimeout(() => { + resolve(ToolInstallationStatus.Installed); + }, 500); + }); + return promise; + } +} \ No newline at end of file diff --git a/extensions/resource-deployment/src/services/tools/dockerTool.ts b/extensions/resource-deployment/src/services/tools/dockerTool.ts new file mode 100644 index 0000000000..d9000920aa --- /dev/null +++ b/extensions/resource-deployment/src/services/tools/dockerTool.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; +import { ToolType, ITool, ToolInstallationStatus } from '../../interfaces'; +import * as nls from 'vscode-nls'; +const localize = nls.loadMessageBundle(); + + +export class DockerTool implements ITool { + get name(): string { + return 'docker'; + } + + get description(): string { + return localize('resourceDeployment.DockerDescription', 'Manages the containers'); + } + + get type(): ToolType { + return ToolType.Docker; + } + + get displayName(): string { + return localize('resourceDeployment.DockerDisplayName', 'Docker'); + } + + get supportAutoInstall(): boolean { + return true; + } + + install(version: string): Thenable { + throw new Error('Method not implemented.'); + } + + getInstallationStatus(versionExpression: string): Thenable { + let promise = new Promise(resolve => { + setTimeout(() => { + resolve(ToolInstallationStatus.Installed); + }, 500); + }); + return promise; + } +} \ No newline at end of file diff --git a/extensions/resource-deployment/src/services/tools/kubectlTool.ts b/extensions/resource-deployment/src/services/tools/kubectlTool.ts new file mode 100644 index 0000000000..35b333f45c --- /dev/null +++ b/extensions/resource-deployment/src/services/tools/kubectlTool.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; +import { ToolType, ITool, ToolInstallationStatus } from '../../interfaces'; +import * as nls from 'vscode-nls'; +const localize = nls.loadMessageBundle(); + +export class KubeCtlTool implements ITool { + get name(): string { + return 'kubectl'; + } + + get description(): string { + return localize('resourceDeployment.KUBECTLDescription', 'Tool used for managing the Kubernetes cluster'); + } + + get type(): ToolType { + return ToolType.KubeCtl; + } + + get displayName(): string { + return localize('resourceDeployment.KUBECTLDisplayName', 'kubectl'); + } + + get supportAutoInstall(): boolean { + return true; + } + + install(version: string): Thenable { + throw new Error('Method not implemented.'); + } + + getInstallationStatus(versionExpression: string): Thenable { + let promise = new Promise(resolve => { + setTimeout(() => { + resolve(ToolInstallationStatus.Installed); + }, 500); + }); + return promise; + } +} \ No newline at end of file diff --git a/extensions/resource-deployment/src/services/tools/mssqlctlTool.ts b/extensions/resource-deployment/src/services/tools/mssqlctlTool.ts new file mode 100644 index 0000000000..6fb45289cf --- /dev/null +++ b/extensions/resource-deployment/src/services/tools/mssqlctlTool.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; +import { ToolType, ITool, ToolInstallationStatus } from '../../interfaces'; +import * as nls from 'vscode-nls'; +const localize = nls.loadMessageBundle(); + +export class MSSQLCtlTool implements ITool { + get name(): string { + return 'mssqlctl'; + } + + get description(): string { + return localize('resourceDeployment.MSSQLCTLDescription', 'Command-line tool for installing and managing the SQL Server big data cluster'); + } + + get type(): ToolType { + return ToolType.MSSQLCtl; + } + + get displayName(): string { + return localize('resourceDeployment.MSSQLCTLDisplayName', 'mssqlctl'); + } + + isInstalled(versionExpression: string): Thenable { + let promise = new Promise(resolve => { + setTimeout(() => { + resolve(true); + }, 500); + }); + return promise; + } + + get supportAutoInstall(): boolean { + return true; + } + + install(version: string): Thenable { + throw new Error('Method not implemented.'); + } + + getInstallationStatus(versionExpression: string): Thenable { + let promise = new Promise(resolve => { + setTimeout(() => { + resolve(ToolInstallationStatus.Installed); + }, 500); + }); + return promise; + } +} \ No newline at end of file diff --git a/extensions/resource-deployment/src/services/tools/pythonTool.ts b/extensions/resource-deployment/src/services/tools/pythonTool.ts new file mode 100644 index 0000000000..4ff698f839 --- /dev/null +++ b/extensions/resource-deployment/src/services/tools/pythonTool.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; +import { ToolType, ITool, ToolInstallationStatus } from '../../interfaces'; +import * as nls from 'vscode-nls'; +const localize = nls.loadMessageBundle(); + + +export class PythonTool implements ITool { + get name(): string { + return 'python'; + } + + get description(): string { + return localize('resourceDeployment.PythonDescription', 'Required by notebook feature'); + } + + get type(): ToolType { + return ToolType.Python; + } + + get displayName(): string { + return localize('resourceDeployment.PythonDisplayName', 'Python'); + } + + get supportAutoInstall(): boolean { + return true; + } + + install(version: string): Thenable { + throw new Error('Method not implemented.'); + } + + getInstallationStatus(versionExpression: string): Thenable { + let promise = new Promise(resolve => { + setTimeout(() => { + resolve(ToolInstallationStatus.Installed); + }, 500); + }); + return promise; + } +} \ No newline at end of file diff --git a/extensions/resource-deployment/src/services/toolsService.ts b/extensions/resource-deployment/src/services/toolsService.ts new file mode 100644 index 0000000000..c440893154 --- /dev/null +++ b/extensions/resource-deployment/src/services/toolsService.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; +import { ToolRequirementInfo, ToolStatusInfo, ITool } from '../interfaces'; +import { PythonTool } from './tools/pythonTool'; +import { DockerTool } from './tools/dockerTool'; +import { AzCliTool } from './tools/azCliTool'; +import { MSSQLCtlTool } from './tools/mssqlCtlTool'; +import { KubeCtlTool } from './tools/kubeCtlTool'; + +export interface IToolsService { + getToolStatus(toolRequirements: ToolRequirementInfo[]): Thenable; + getToolByName(toolName: string): ITool | undefined; +} + +export class ToolsService implements IToolsService { + private static readonly SupportedTools: ITool[] = [new PythonTool(), new DockerTool(), new AzCliTool(), new MSSQLCtlTool(), new KubeCtlTool()]; + + getToolStatus(toolRequirements: ToolRequirementInfo[]): Thenable { + const toolStatusList: ToolStatusInfo[] = []; + let promises = []; + for (let i = 0; i < toolRequirements.length; i++) { + const toolRequirement = toolRequirements[i]; + const tool = this.getToolByName(toolRequirement.name); + if (tool !== undefined) { + promises.push(tool.getInstallationStatus(toolRequirement.version).then(installStatus => { + toolStatusList.push({ + name: tool.displayName, + description: tool.description, + status: installStatus, + version: toolRequirement.version + }); + })); + } + } + return Promise.all(promises).then(() => { return toolStatusList; }); + } + + getToolByName(toolName: string): ITool | undefined { + if (toolName) { + for (let i = 0; i < ToolsService.SupportedTools.length; i++) { + if (toolName === ToolsService.SupportedTools[i].name) { + return ToolsService.SupportedTools[i]; + } + } + } + return undefined; + } + +} \ No newline at end of file diff --git a/extensions/resource-deployment/src/test/index.ts b/extensions/resource-deployment/src/test/index.ts new file mode 100644 index 0000000000..f525a8cf52 --- /dev/null +++ b/extensions/resource-deployment/src/test/index.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const path = require('path'); +const testRunner = require('vscode/lib/testrunner'); + +const suite = 'Resource Deployment Unit Tests'; + +const testOptions: any = { + ui: 'tdd', + useColors: true, + timeout: 60000 +}; + +if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) { + testOptions.reporter = 'mocha-multi-reporters'; + testOptions.reporterOptions = { + reporterEnabled: 'spec, mocha-junit-reporter', + mochaJunitReporterReporterOptions: { + testsuitesTitle: `${suite} ${process.platform}`, + mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`) + } + }; +} + +testRunner.configure(testOptions); + +export = testRunner; diff --git a/extensions/resource-deployment/src/test/notebookService.test.ts b/extensions/resource-deployment/src/test/notebookService.test.ts new file mode 100644 index 0000000000..c5afc79274 --- /dev/null +++ b/extensions/resource-deployment/src/test/notebookService.test.ts @@ -0,0 +1,130 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as TypeMoq from 'typemoq'; +import 'mocha'; +import * as path from 'path'; +import * as os from 'os'; +import { NotebookService } from '../services/NotebookService'; +import assert = require('assert'); +import { NotebookInfo } from '../interfaces'; +import { IPlatformService } from '../services/platformService'; + +suite('Notebook Service Tests', function (): void { + + test('getNotebook with string parameter', () => { + const mockPlatformService = TypeMoq.Mock.ofType(); + const notebookService = new NotebookService(mockPlatformService.object); + const notebookInput = 'test-notebook.ipynb'; + mockPlatformService.setup((service) => service.platform()).returns(() => { return 'win32'; }); + let returnValue = notebookService.getNotebook(notebookInput); + assert.equal(returnValue, notebookInput, 'returned notebook name does not match expected value'); + mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.never()); + + mockPlatformService.reset(); + mockPlatformService.setup((service) => service.platform()).returns(() => { return 'win32'; }); + returnValue = notebookService.getNotebook(''); + assert.equal(returnValue, '', 'returned notebook name does not match expected value is not an empty string'); + mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.never()); + }); + + test('getNotebook with NotebookInfo parameter', () => { + const mockPlatformService = TypeMoq.Mock.ofType(); + const notebookService = new NotebookService(mockPlatformService.object); + const notebookWin32 = 'test-notebook-win32.ipynb'; + const notebookDarwin = 'test-notebook-darwin.ipynb'; + const notebookLinux = 'test-notebook-linux.ipynb'; + + const notebookInput: NotebookInfo = { + darwin: notebookDarwin, + win32: notebookWin32, + linux: notebookLinux + }; + mockPlatformService.setup((service) => service.platform()).returns(() => { return 'win32'; }); + let returnValue = notebookService.getNotebook(notebookInput); + assert.equal(returnValue, notebookWin32, 'returned notebook name does not match expected value for win32 platform'); + mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.once()); + + mockPlatformService.reset(); + mockPlatformService.setup((service) => service.platform()).returns(() => { return 'darwin'; }); + returnValue = notebookService.getNotebook(notebookInput); + assert.equal(returnValue, notebookDarwin, 'returned notebook name does not match expected value for darwin platform'); + mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.once()); + + mockPlatformService.reset(); + mockPlatformService.setup((service) => service.platform()).returns(() => { return 'linux'; }); + returnValue = notebookService.getNotebook(notebookInput); + assert.equal(returnValue, notebookLinux, 'returned notebook name does not match expected value for linux platform'); + mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.once()); + }); + + test('launchNotebook', () => { + const mockPlatformService = TypeMoq.Mock.ofType(); + const notebookService = new NotebookService(mockPlatformService.object); + const notebookFileName = 'mynotebook.ipynb'; + const notebookPath = `./notebooks/${notebookFileName}`; + + let actualSourceFile; + const expectedSourceFile = path.join(__dirname, '../../', notebookPath); + let actualTargetFile; + const expectedTargetFile = path.join(os.homedir(), notebookFileName); + mockPlatformService.setup((service) => service.platform()).returns(() => { return 'win32'; }); + mockPlatformService.setup((service) => service.openFile(TypeMoq.It.isAnyString())); + mockPlatformService.setup((service) => service.fileExists(TypeMoq.It.isAnyString())) + .returns((path) => { + if (path === expectedSourceFile) { + return true; + } + return false; + }); + mockPlatformService.setup((service) => service.copyFile(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString())) + .returns((source, target) => { actualSourceFile = source; actualTargetFile = target; }); + notebookService.launchNotebook(notebookPath); + mockPlatformService.verify((service) => service.copyFile(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString()), TypeMoq.Times.once()); + mockPlatformService.verify((service) => service.openFile(TypeMoq.It.isAnyString()), TypeMoq.Times.once()); + assert.equal(actualSourceFile, expectedSourceFile, 'source file is not correct'); + assert.equal(actualTargetFile, expectedTargetFile, 'target file is not correct'); + }); + + test('getTargetNotebookFileName with no name conflict', () => { + const mockPlatformService = TypeMoq.Mock.ofType(); + const notebookService = new NotebookService(mockPlatformService.object); + const notebookFileName = 'mynotebook.ipynb'; + const sourceNotebookPath = `./notebooks/${notebookFileName}`; + + const expectedTargetFile = path.join(os.homedir(), notebookFileName); + mockPlatformService.setup((service) => service.fileExists(TypeMoq.It.isAnyString())) + .returns((path) => { return false; }); + const actualFileName = notebookService.getTargetNotebookFileName(sourceNotebookPath, os.homedir()); + mockPlatformService.verify((service) => service.fileExists(TypeMoq.It.isAnyString()), TypeMoq.Times.once()); + assert.equal(actualFileName, expectedTargetFile, 'target file name is not correct'); + }); + + test('getTargetNotebookFileName with name conflicts', () => { + const mockPlatformService = TypeMoq.Mock.ofType(); + const notebookService = new NotebookService(mockPlatformService.object); + const notebookFileName = 'mynotebook.ipynb'; + const sourceNotebookPath = `./notebooks/${notebookFileName}`; + const expectedFileName = 'mynotebook-2.ipynb'; + + const expected1stAttemptTargetFile = path.join(os.homedir(), notebookFileName); + const expected2ndAttemptTargetFile = path.join(os.homedir(), 'mynotebook-1.ipynb'); + const expectedTargetFile = path.join(os.homedir(), expectedFileName); + mockPlatformService.setup((service) => service.fileExists(TypeMoq.It.isAnyString())) + .returns((path) => { + // list all the possible values here and handle them + // if we only handle the expected value and return true for anything else, the test might run forever until times out + if (path === expected1stAttemptTargetFile || path === expected2ndAttemptTargetFile) { + return true; + } + return false; + }); + const actualFileName = notebookService.getTargetNotebookFileName(sourceNotebookPath, os.homedir()); + mockPlatformService.verify((service) => service.fileExists(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(3)); + assert.equal(actualFileName, expectedTargetFile, 'target file name is not correct'); + }); +}); \ No newline at end of file diff --git a/extensions/resource-deployment/src/ui/resourceDeploymentDialog.ts b/extensions/resource-deployment/src/ui/resourceDeploymentDialog.ts index d880ec31e0..282ae9b040 100644 --- a/extensions/resource-deployment/src/ui/resourceDeploymentDialog.ts +++ b/extensions/resource-deployment/src/ui/resourceDeploymentDialog.ts @@ -6,26 +6,210 @@ import * as azdata from 'azdata'; import * as nls from 'vscode-nls'; +import { IResourceTypeService } from '../services/resourceTypeService'; +import * as vscode from 'vscode'; +import { ResourceType, DeploymentProvider, ToolInstallationStatus } from '../interfaces'; +import { IToolsService } from '../services/toolsService'; +import { INotebookService } from '../services/notebookService'; + const localize = nls.loadMessageBundle(); export class ResourceDeploymentDialog { - private dialogObject: azdata.window.Dialog; + private _selectedResourceType: ResourceType; + private _toDispose: vscode.Disposable[] = []; + private _dialogObject: azdata.window.Dialog; + private _resourceTypeCards: azdata.CardComponent[] = []; + private _view!: azdata.ModelView; + private _resourceDescriptionLabel!: azdata.TextComponent; + private _optionsContainer!: azdata.FlexContainer; + private _toolsTable!: azdata.TableComponent; + private _cardResourceTypeMap: Map = new Map(); + private _optionDropDownMap: Map = new Map(); - constructor() { - this.dialogObject = azdata.window.createModelViewDialog(localize('deploymentDialog.title', 'Install SQL Server'), 'resourceDeploymentDialog', true); + constructor(private context: vscode.ExtensionContext, + private notebookService: INotebookService, + private toolsService: IToolsService, + private resourceTypeService: IResourceTypeService, + resourceType: ResourceType) { + this._selectedResourceType = resourceType; + this._dialogObject = azdata.window.createModelViewDialog(localize('deploymentDialog.title', 'Select a configuration'), 'resourceDeploymentDialog', true); + this._dialogObject.cancelButton.onClick(() => this.onCancel()); + this._dialogObject.okButton.label = localize('deploymentDialog.OKButtonText', 'Select'); + this._dialogObject.okButton.onClick(() => this.onComplete()); } private initializeDialog() { let tab = azdata.window.createTab(''); tab.registerContent((view: azdata.ModelView) => { - let text = view.modelBuilder.text().withProperties({ value: 'place holder' }).component(); - return view.initializeModel(text); + this._view = view; + this.resourceTypeService.getResourceTypes().forEach(resourceType => this.addCard(resourceType)); + const cardsContainer = view.modelBuilder.flexContainer().withItems(this._resourceTypeCards, { flex: '0 0 auto', CSSStyles: { 'margin-bottom': '10px' } }).withLayout({ flexFlow: 'row', alignItems: 'left' }).component(); + this._resourceDescriptionLabel = view.modelBuilder.text().withProperties({ value: this._selectedResourceType ? this._selectedResourceType.description : undefined }).component(); + this._optionsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component(); + + const toolColumn: azdata.TableColumn = { + value: localize('deploymentDialog.toolNameColumnHeader', 'Tool'), + width: 100 + }; + const descriptionColumn: azdata.TableColumn = { + value: localize('deploymentDialog.toolDescriptionColumnHeader', 'Description'), + width: 500 + }; + const versionColumn: azdata.TableColumn = { + value: localize('deploymentDialog.toolVersionColumnHeader', 'Version'), + width: 200 + }; + const statusColumn: azdata.TableColumn = { + value: localize('deploymentDialog.toolStatusColumnHeader', 'Status'), + width: 200 + }; + + this._toolsTable = view.modelBuilder.table().withProperties({ + height: 150, + data: [], + columns: [toolColumn, descriptionColumn, versionColumn, statusColumn], + width: 1000 + }).component(); + + const formBuilder = view.modelBuilder.formContainer().withFormItems( + [ + { + component: cardsContainer, + title: '' + }, { + component: this._resourceDescriptionLabel, + title: '' + }, { + component: this._optionsContainer, + title: localize('deploymentDialog.OptionsTitle', 'Options') + }, { + component: this._toolsTable, + title: localize('deploymentDialog.RequiredToolsTitle', 'Required tools') + } + ], + { + horizontal: false + } + ); + + const form = formBuilder.withLayout({ width: '100%' }).component(); + + if (this._selectedResourceType) { + this.selectResourceType(this._selectedResourceType); + } + + return view.initializeModel(form); }); - this.dialogObject.content = [tab]; + this._dialogObject.content = [tab]; } public open(): void { this.initializeDialog(); - azdata.window.openDialog(this.dialogObject); + azdata.window.openDialog(this._dialogObject); + } + + private addCard(resourceType: ResourceType): void { + const card = this._view.modelBuilder.card().withProperties({ + cardType: azdata.CardType.VerticalButton, + iconPath: { + dark: this.context.asAbsolutePath(resourceType.icon.dark), + light: this.context.asAbsolutePath(resourceType.icon.light) + }, + label: resourceType.displayName, + selected: (this._selectedResourceType && this._selectedResourceType.name === resourceType.name) + }).component(); + + this._resourceTypeCards.push(card); + this._cardResourceTypeMap.set(resourceType.name, card); + this._toDispose.push(card.onCardSelectedChanged(() => this.selectResourceType(resourceType))); + } + + private selectResourceType(resourceType: ResourceType): void { + this._selectedResourceType = resourceType; + const card = this._cardResourceTypeMap.get(this._selectedResourceType.name)!; + if (card.selected) { + // clear the selected state of the previously selected card + this._resourceTypeCards.forEach(c => { + if (c !== card) { + c.selected = false; + } + }); + } else { + // keep the selected state if no other card is selected + if (this._resourceTypeCards.filter(c => { return c !== card && c.selected; }).length === 0) { + card.selected = true; + } + } + + this._resourceDescriptionLabel.value = resourceType.description; + this._optionsContainer.clearItems(); + this._optionDropDownMap.clear(); + resourceType.options.forEach(option => { + const optionLabel = this._view.modelBuilder.text().withProperties({ + value: option.displayName + }).component(); + optionLabel.width = '150px'; + + const optionSelectBox = this._view.modelBuilder.dropDown().withProperties({ + values: option.values, + value: option.values[0], + width: '300px' + }).component(); + + this._toDispose.push(optionSelectBox.onValueChanged(() => { this.updateTools(); })); + this._optionDropDownMap.set(option.name, optionSelectBox); + const row = this._view.modelBuilder.flexContainer().withItems([optionLabel, optionSelectBox], { flex: '0 0 auto', CSSStyles: { 'margin-right': '20px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component(); + this._optionsContainer.addItem(row); + }); + this.updateTools(); + } + + private updateTools(): void { + this.toolsService.getToolStatus(this.getCurrentProvider().requiredTools).then(toolStatus => { + let tableData = toolStatus.map(tool => { + return [tool.name, tool.description, tool.version, this.getToolStatusText(tool.status)]; + }); + this._toolsTable.data = tableData; + }); + } + + private getToolStatusText(status: ToolInstallationStatus): string { + switch (status) { + case ToolInstallationStatus.Installed: + return '✔️ ' + localize('deploymentDialog.InstalledText', 'Installed'); + case ToolInstallationStatus.NotInstalled: + return '❌ ' + localize('deploymentDialog.NotInstalledText', 'Not Installed'); + case ToolInstallationStatus.Installing: + return '⌛ ' + localize('deploymentDialog.InstallingText', 'Installing…'); + case ToolInstallationStatus.FailedToInstall: + return '❌ ' + localize('deploymentDialog.FailedToInstallText', 'Install Failed'); + default: + return 'unknown status'; + } + } + + private getCurrentProvider(): DeploymentProvider { + const options: { option: string, value: string }[] = []; + + this._optionDropDownMap.forEach((selectBox, option) => { + let selectedValue: azdata.CategoryValue = selectBox.value as azdata.CategoryValue; + options.push({ option: option, value: selectedValue.name }); + }); + + return this._selectedResourceType.getProvider(options)!; + } + + private onCancel(): void { + this.dispose(); + } + + private onComplete(): void { + const provider = this.getCurrentProvider(); + this.notebookService.launchNotebook(provider.notebook); + this.dispose(); + } + + private dispose(): void { + this._toDispose.forEach(disposable => disposable.dispose()); } } diff --git a/extensions/resource-deployment/yarn.lock b/extensions/resource-deployment/yarn.lock index 45f8b9278d..f25cf91f7e 100644 --- a/extensions/resource-deployment/yarn.lock +++ b/extensions/resource-deployment/yarn.lock @@ -2,7 +2,629 @@ # yarn lockfile v1 +agent-base@4, agent-base@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" + integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== + dependencies: + es6-promisify "^5.0.0" + +ajv@^6.5.5: + version "6.10.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1" + integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg== + 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" + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" + integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +browser-stdout@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" + integrity sha1-81HTKWnTL6XXpVZxVCY9korjvR8= + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +circular-json@^0.3.1: + version "0.3.3" + resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" + integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A== + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" + integrity sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +debug@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + +debug@^3.1.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +diff@3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" + integrity sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww== + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +es6-promise@^4.0.3: + version "4.2.6" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.6.tgz#b685edd8258886365ea62b57d30de28fadcd974f" + integrity sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q== + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= + dependencies: + es6-promise "^4.0.3" + +escape-string-regexp@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +fast-deep-equal@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= + +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +glob@7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== + dependencies: + 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" + +glob@^7.1.2: + version "7.1.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" + integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +growl@1.10.3: + version "1.10.3" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f" + integrity sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q== + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.0: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + dependencies: + ajv "^6.5.5" + har-schema "^2.0.0" + +has-flag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + integrity sha1-6CB68cx7MNRGzHC3NLXovhj4jVE= + +he@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" + integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= + +http-proxy-agent@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" + integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== + dependencies: + agent-base "4" + debug "3.1.0" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +https-proxy-agent@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0" + integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ== + dependencies: + agent-base "^4.1.0" + debug "^3.1.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +lodash@^4.17.4: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== + +mime-db@1.40.0: + version "1.40.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" + integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.24" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" + integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ== + dependencies: + mime-db "1.40.0" + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +mkdirp@0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +mocha@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-4.1.0.tgz#7d86cfbcf35cb829e2754c32e17355ec05338794" + integrity sha512-0RVnjg1HJsXY2YFDoTNzcc1NKhYuXKRrBAG2gDygmJJA136Cs2QlRliZG1mA0ap7cuaT30mw16luAeln+4RiNA== + dependencies: + browser-stdout "1.3.0" + commander "2.11.0" + debug "3.1.0" + diff "3.3.1" + escape-string-regexp "1.0.5" + glob "7.1.2" + growl "1.10.3" + he "1.1.1" + mkdirp "0.5.1" + supports-color "4.4.0" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +postinstall-build@^5.0.1: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postinstall-build/-/postinstall-build-5.0.3.tgz#238692f712a481d8f5bc8960e94786036241efc7" + integrity sha512-vPvPe8TKgp4FLgY3+DfxCE5PIfoXBK2lyLfNCxsRbDsV6vS4oU5RG/IWxrblMn6heagbnMED3MemUQllQ2bQUg== + +psl@^1.1.24: + version "1.1.31" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184" + integrity sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw== + +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +querystringify@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" + integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== + +request@^2.88.0: + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" + integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.0" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.4.3" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= + +safe-buffer@^5.0.1, safe-buffer@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^5.4.1: + version "5.7.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" + integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== + +source-map-support@^0.5.0: + version "0.5.12" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599" + integrity sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +supports-color@4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" + integrity sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ== + dependencies: + has-flag "^2.0.0" + +tough-cookie@~2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== + dependencies: + psl "^1.1.24" + punycode "^1.4.1" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +typemoq@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/typemoq/-/typemoq-2.1.0.tgz#4452ce360d92cf2a1a180f0c29de2803f87af1e8" + integrity sha512-DtRNLb7x8yCTv/KHlwes+NI+aGb4Vl1iPC63Hhtcvk1DpxSAZzKWQv0RQFY0jX2Uqj0SDBNl8Na4e6MV6TNDgw== + dependencies: + circular-json "^0.3.1" + lodash "^4.17.4" + postinstall-build "^5.0.1" + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +url-parse@^1.4.4: + version "1.4.7" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" + integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + +uuid@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + vscode-nls@^3.2.1: version "3.2.5" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.5.tgz#25520c1955108036dec607c85e00a522f247f1a4" integrity sha512-ITtoh3V4AkWXMmp3TB97vsMaHRgHhsSFPsUdzlueSL+dRZbSNTZeOmdQv60kjCV306ghPxhDeoNUEm3+EZMuyw== + +vscode-test@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/vscode-test/-/vscode-test-0.4.1.tgz#5e2387dbc303544c932092469e6bbf42204bfab3" + integrity sha512-uIi/07uG/gmCbD9Y9bFpNzmk4el82xiclijEdL426A3jOFfvwdqgfmtuWYfxEGo0w6JY9EqVDTGQCXwuInXVTQ== + dependencies: + http-proxy-agent "^2.1.0" + https-proxy-agent "^2.2.1" + +vscode@^1.1.26: + version "1.1.34" + resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.1.34.tgz#3aba5d2f3a9d43f4e798f6933339fe5fcfb782c6" + integrity sha512-GuT3tCT2N5Qp26VG4C+iGmWMgg/MuqtY5G5TSOT3U/X6pgjM9LFulJEeqpyf6gdzpI4VyU3ZN/lWPo54UFPuQg== + dependencies: + glob "^7.1.2" + mocha "^4.0.1" + request "^2.88.0" + semver "^5.4.1" + source-map-support "^0.5.0" + url-parse "^1.4.4" + vscode-test "^0.4.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= diff --git a/scripts/test-extensions-unit.bat b/scripts/test-extensions-unit.bat index 2571a33b4e..8952929e0d 100644 --- a/scripts/test-extensions-unit.bat +++ b/scripts/test-extensions-unit.bat @@ -15,6 +15,7 @@ call .\scripts\code.bat --extensionDevelopmentPath=%~dp0\..\extensions\agent --e call .\scripts\code.bat --extensionDevelopmentPath=%~dp0\..\extensions\cms --extensionTestsPath=%~dp0\..\extensions\cms\out\test --user-data-dir=%VSCODEUSERDATADIR% --extensions-dir=%VSCODEEXTENSIONSDIR% --remote-debugging-port=9222 call .\scripts\code.bat --extensionDevelopmentPath=%~dp0\..\extensions\dacpac --extensionTestsPath=%~dp0\..\extensions\dacpac\out\test --user-data-dir=%VSCODEUSERDATADIR% --extensions-dir=%VSCODEEXTENSIONSDIR% --remote-debugging-port=9222 call .\scripts\code.bat --extensionDevelopmentPath=%~dp0\..\extensions\notebook --extensionTestsPath=%~dp0\..\extensions\notebook\out\test --user-data-dir=%VSCODEUSERDATADIR% --extensions-dir=%VSCODEEXTENSIONSDIR% --remote-debugging-port=9222 +call .\scripts\code.bat --extensionDevelopmentPath=%~dp0\..\extensions\resource-deployment --extensionTestsPath=%~dp0\..\extensions\resource-deployment\out\test --user-data-dir=%VSCODEUSERDATADIR% --extensions-dir=%VSCODEEXTENSIONSDIR% --remote-debugging-port=9222 if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/scripts/test-extensions-unit.sh b/scripts/test-extensions-unit.sh index e3f01e8ece..b080f05b9c 100644 --- a/scripts/test-extensions-unit.sh +++ b/scripts/test-extensions-unit.sh @@ -23,6 +23,7 @@ echo $VSCODEEXTDIR ./scripts/code.sh --extensionDevelopmentPath=$ROOT/extensions/cms --extensionTestsPath=$ROOT/extensions/cms/out/test --user-data-dir=$VSCODEUSERDATADIR --extensions-dir=$VSCODEEXTDIR ./scripts/code.sh --extensionDevelopmentPath=$ROOT/extensions/dacpac --extensionTestsPath=$ROOT/extensions/dacpac/out/test --user-data-dir=$VSCODEUSERDATADIR --extensions-dir=$VSCODEEXTDIR ./scripts/code.sh --extensionDevelopmentPath=$ROOT/extensions/notebook --extensionTestsPath=$ROOT/extensions/notebook/out/test --user-data-dir=$VSCODEUSERDATADIR --extensions-dir=$VSCODEEXTDIR +./scripts/code.sh --extensionDevelopmentPath=$ROOT/extensions/resource-deployment --extensionTestsPath=$ROOT/extensions/resource-deployment/out/test --user-data-dir=$VSCODEUSERDATADIR --extensions-dir=$VSCODEEXTDIR rm -r $VSCODEUSERDATADIR rm -r $VSCODEEXTDIR