diff --git a/.dockerignore b/.dockerignore index 1dfe466b59..677b545415 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,9 +1,30 @@ .dockerignore .git +.github .gitignore .vs .vscode -packages +.travis.yml +.codecov.yml +appveyor.yml +LICENSE +README +build/** +!build/analyzers.ruleset +!build/tgs.docker.sh +!build/tgs.ico +!build/stylecop.json */bin */obj tests +docs +tools +src/DMAPI +src/Tgstation.Server.Host/ClientApp/build +src/Tgstation.Server.Host/ClientApp/node_modules +src/Tgstation.Server.Host/wwwroot +src/Tgstation.Server.Host/appsettings.Development.json +src/Tgstation.Server.Host/tgs.bat +src/Tgstation.Server.Host/tgs.sh +src/Tgstation.Server.Host.Client +src/Tgstation.Server.Host.Service diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index e1c6d6cf23..05fd2a175c 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -34,7 +34,7 @@ You can of course, as always, ask for help at [#coderbus](irc://irc.rizon.net/co ### Development Environment -You need the Dotnet 2.1 SDK to compile the main server and command line programs. In order to build the service version you also need a .NET 4.7.1 build chain +You need the Dotnet 2.1 SDK and npm>=v5.7 (in your PATH) to compile the server. In order to build the service version you also need a .NET 4.7.1 build chain The recommended IDE is Visual Studio 2017 which has installation options for both of these. @@ -47,11 +47,6 @@ As mentioned before, you are expected to follow these specifications in order to ### Object Oriented Code As C# is an object-oriented language, code must be object-oriented when possible in order to be more flexible when adding content to it. If you don't know what "object-oriented" means, we highly recommend you do some light research to grasp the basics. -### Tabs, not spaces -You must use tabs to indent your code, NOT SPACES. - -(You may use spaces to align something, but you should tab to the block level first, then add the remaining spaces) - ### No hacky code Hacky code, such as adding specific checks, is highly discouraged and only allowed when there is ***no*** other option. (Protip: 'I couldn't immediately think of a proper way so thus there must be no other option' is not gonna cut it here! If you can't think of anything else, say that outright and admit that you need help with it. Maintainers exist for exactly that reason.) @@ -97,7 +92,7 @@ The version format we use is 4.\.\.\. The first number n ### Formatting -The formatting style is the same one Visual studio uses by default. Quick formatting of code blocks can be achieved by deleting and retyping the trailing `}` +Stylecop will throw warnings if your code does not match style guidelines. Do NOT suppress these ### Use early return Do not enclose a function in an if-block when returning on a condition is more feasible @@ -149,7 +144,32 @@ There is no strict process when it comes to merging pull requests. Pull requests * Commits MUST be properly titled and commented as we only use merge commits for the pull request process +## Deployment process + +Every issue/pull request in a release should share a common milestone named with the release version i.e. `4.5.3.5` + +When every issue and PR in the milestone is closed. Create a version bump PR that changes the version numbers. At the time of this writing they exist in the following files + +- `/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj` +- `/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj` +- 2 in `/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs` +- `/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj` + +Merge the pull request with `[TGSDeploy]` somewhere in the commit title. The scripts will handle amalgamating release notes, building, closing the milestone, and publishing the release. This will also trigger update notifications on existing TGS deployments. + +### API/Client Deployment + +The Api/Client project versions must be updated on nuget when changed. The numbers exist in the following files: + +- `/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj` +- `/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj` + +These should not be the same numbers as the main suite. When bumping the API version the client should only receive a minor (3rd number) version bump unless major changes to CLIENT code were made. + +Merge these alongside regular deployments with `[NugetDeploy]` in the commit title (Won't work without an accompanying `[TGSDeploy]`). This will handle the nuget publishing. + ## Banned content + Do not add any of the following in a Pull Request or risk getting the PR closed: * National Socialist Party of Germany content, National Socialist Party of Germany related content, or National Socialist Party of Germany references diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 8e7e9eb24b..4b7d54e023 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,3 +1,5 @@ +If your issue is for the web control panel, please report it here instead: https://github.com/tgstation/tgstation-server-control-panel + Please include: - A description of the issue diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..98fe22ab33 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,13 @@ +[Release Notes]: # (Your PR should contain a detailed list of notable changes, titled appropriately. This includes any observable changes to the server or DMAPI. See example below) + +:cl: +Description of your change +Each newline corresponds to a release note in the upcoming sprint +/:cl: + +:cl: +You can also have multiple sets of release notes per pull request +They will be amalgamated together in the end +/:cl: + +[Why]: # (Please add a short description [two lines down] of why you think these changes would benefit the game. If you can't justify it in words, it might not be worth adding.) diff --git a/.gitignore b/.gitignore index 20bc8fcd5a..fefd0b5b91 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,7 @@ artifacts/ /tests/DMAPI/travistester.int /tests/DMAPI/travistester.dmb /src/Tgstation.Server.Host/appsettings.Development.json +/src/Tgstation.Server.Host/wwwroot +/src/Tgstation.Server.Host/ClientApp/node_modules +/src/Tgstation.Server.Host/ClientApp/build +/tools/ReleaseNotes/release_notes.md diff --git a/README.md b/README.md index 258a1d3128..c94b74cc94 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,12 @@ Create an `appsettings.Production.json` file next to `appsettings.json`. This wi - `Database:ConnectionString`: Connection string for your database. Click [here](https://www.developerfusion.com/tools/sql-connection-string/) for an SQL Server generator or see [here](https://www.connectionstrings.com/mysql/) for a MySQL guide ([You should probably use '127.0.0.1' instead of 'localhost'](https://stackoverflow.com/questions/19712307/mysql-localhost-127-0-0-1)). +- `ControlPanel:Enable`: Enable the javascript based control panel to be served from the server via /index.html + +- `ControlPanel:AllowAnyOrigin`: Set the Access-Control-Allow-Origin header to * for all responses (also enables all headers and methods) + +- `ControlPanel:AllowedOrigins`: Set the Access-Control-Allow-Origin headers to this list of origins for all responses (also enables all headers and methods). This is overridden by `ControlPanel:AllowAnyOrigin` + ### Database Configuration If using a MariaDB/MySQL server, our client library [recommends you set 'utf8mb4' as your default charset](https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql#1-recommended-server-charset) disregard at your own risk. diff --git a/appveyor.yml b/appveyor.yml index bb91fa6517..d6362a9ef2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,6 +6,8 @@ environment: TGS4_TEST_CONNECTION_STRING: Server=(local)\SQL2017;Initial Catalog=TGS_Test;User ID=sa;Password=Password12! TGS4_TEST_GITHUB_TOKEN: secure: lJNGAXwiB5HlWdthz3K4PetqpTG5IEAyRgKaiKxFMQ8HW8CcOjRtB97B05op7BsK + TGS_RELEASE_NOTES_TOKEN: + secure: lJNGAXwiB5HlWdthz3K4PetqpTG5IEAyRgKaiKxFMQ8HW8CcOjRtB97B05op7BsK branches: only: - master @@ -24,6 +26,8 @@ artifacts: name: ServerUpdatePackage - path: src/DMAPI name: DMAPI + - path: tgsdox + name: DocumentationHtml cache: - ~\.nuget\packages -> **\*.csproj - C:\ProgramData\chocolatey\bin -> appveyor.yml @@ -33,6 +37,7 @@ services: install: - choco install doxygen.portable codecov graphviz.portable opencover.portable - nuget restore tgstation-server.sln + - ps: Install-Product node 10 build: project: tgstation-server.sln parallel: false @@ -77,17 +82,16 @@ after_test: - ps: Move-Item -path artifacts/ServerService/lib/Default/appsettings.json -destination artifacts/ServerService/ - ps: Remove-Item artifacts/ServerHost/appsettings.json #deploy stuff - - ps: $env:TGSVersion = [System.Diagnostics.FileVersionInfo]::GetVersionInfo("$env:APPVEYOR_BUILD_FOLDER/artifacts/ServerHost/Tgstation.Server.Host.dll").FileVersion - - ps: if($env:APPVEYOR_REPO_COMMIT_MESSAGE -match "\[TGSDeploy\]"){ if($env:APPVEYOR_REPO_BRANCH -match "master"){ if($env:CONFIGURATION -match "Release"){ $env:TGSDeploy = "Do it." }}} - - ps: if($env:APPVEYOR_REPO_COMMIT_MESSAGE -match "\[NugetDeploy\]"){ if($env:APPVEYOR_REPO_BRANCH -match "master"){ if($env:CONFIGURATION -match "Release"){ $env:NugetDeploy = "Do it." }}} + - ps: build/prep_deployment.ps1 deploy: - provider: GitHub release: "tgstation-server-v$(TGSVersion)" - description: 'The /tg/station server suite' + description: "$(TGSReleaseNotes)" auth_token: secure: lJNGAXwiB5HlWdthz3K4PetqpTG5IEAyRgKaiKxFMQ8HW8CcOjRtB97B05op7BsK artifact: ServerConsole,ServerService,ServerUpdatePackage,DMAPI - draft: true + draft: $(TGSDraftNotes) + prerelease: true on: TGSDeploy: "Do it." - provider: NuGet diff --git a/build/BuildDox.ps1 b/build/BuildDox.ps1 index 9087e16101..62288118ac 100644 --- a/build/BuildDox.ps1 +++ b/build/BuildDox.ps1 @@ -32,4 +32,6 @@ if($publish_dox){ git commit -m "Deploy code docs to GitHub Pages for Appveyor build $Env:APPVEYOR_BUILD_NUMBER" -m "Commit: $Env:APPVEYOR_REPO_COMMIT" git push -f "https://$Env:TGS4_TEST_GITHUB_TOKEN@$github_url" 2>&1 | out-null cd "$bf" + rm -r "$doxdir/.git" } +mv C:/tgsdox "$bf/tgsdox" diff --git a/build/Dockerfile b/build/Dockerfile index 108f7ddfbf..420c82b0f9 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -1,5 +1,23 @@ FROM microsoft/dotnet:2.1-sdk AS build +# install node and npm +# replace shell with bash so we can source files +RUN curl --silent -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.2/install.sh | sh + +ENV NODE_VERSION=10.13.0 +ENV NVM_DIR /root/.nvm + +RUN . $NVM_DIR/nvm.sh \ + && nvm install $NODE_VERSION \ + && nvm use $NODE_VERSION \ + && apt-get update \ + && apt-get install -y \ + dos2unix \ + && rm -rf /var/lib/apt/lists/* + +ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules +ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH + WORKDIR /src COPY tgstation-server.sln ./ @@ -13,6 +31,9 @@ RUN dotnet restore -nowarn:MSB3202,nu1503 -p:RestoreUseSkipNonexistentTargets=fa COPY . . +#run dos2unix on tgs.docker.sh so we can build without issue on windows +RUN dos2unix build/tgs.docker.sh + WORKDIR /src/src/Tgstation.Server.Host.Console RUN dotnet publish -c Release -o /app diff --git a/build/analyzers.ruleset b/build/analyzers.ruleset new file mode 100644 index 0000000000..75495a4ace --- /dev/null +++ b/build/analyzers.ruleset @@ -0,0 +1,1057 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/prep_deployment.ps1 b/build/prep_deployment.ps1 new file mode 100644 index 0000000000..a00431bff8 --- /dev/null +++ b/build/prep_deployment.ps1 @@ -0,0 +1,25 @@ +$env:TGSVersion = [System.Diagnostics.FileVersionInfo]::GetVersionInfo("$env:APPVEYOR_BUILD_FOLDER/artifacts/ServerHost/Tgstation.Server.Host.dll").FileVersion + +Write-Host "TGS Version: $env:TGSVersion" + +if (($env:CONFIGURATION -match "Release") -And ($env:APPVEYOR_REPO_BRANCH -match "master") -And ($env:APPVEYOR_REPO_COMMIT_MESSAGE -match "\[TGSDeploy\]")) { + Write-Host "Deploying..." + $env:TGSDeploy = "Do it." + + Write-Host "Generating release notes..." + dotnet run -p "$env:APPVEYOR_BUILD_FOLDER/tools/ReleaseNotes" $env:TGSVersion + $env:TGSDraftNotes = !($?) + $releaseNotesPath = "$env:APPVEYOR_BUILD_FOLDER/tools/ReleaseNotes/release_notes.md" + if (Test-Path $releaseNotesPath -PathType Leaf) { + $env:TGSReleaseNotes = [IO.File]::ReadAllText($releaseNotesPath) + } + else { + Write-Host "Release note generation failed, release will be created as a draft!" + $env:TGSReleaseNotes = "Automatic generation failed, please fill manually!" + } + + if ($env:APPVEYOR_REPO_COMMIT_MESSAGE -match "\[NugetDeploy\]") { + $env:NugetDeploy = "Do it." + Write-Host "Nuget deployment enabled" + } +} diff --git a/build/stylecop.json b/build/stylecop.json new file mode 100644 index 0000000000..e683e4e39b --- /dev/null +++ b/build/stylecop.json @@ -0,0 +1,8 @@ +{ + "settings": { + "indentation": { + "tabSize": 4, + "useTabs": true + } + } +} diff --git a/build/test_core.sh b/build/test_core.sh index b361813ef7..3418f12fe4 100755 --- a/build/test_core.sh +++ b/build/test_core.sh @@ -5,6 +5,8 @@ dotnet tool install --global coverlet.console mkdir TestResults +source ~/.nvm/nvm.sh && nvm install 10 + cd tests/Tgstation.Server.Api.Tests dotnet build -c $CONFIG /p:CopyLocalLockFileAssemblies=true diff --git a/build/tgs.docker.sh b/build/tgs.docker.sh index cc4cf6fd4e..f9904700dd 100755 --- a/build/tgs.docker.sh +++ b/build/tgs.docker.sh @@ -1,6 +1,9 @@ #!/bin/sh -touch /config_data/appsettings.Production.json -ln -s /config_data/appsettings.Production.json /app/appsettings.Production.json +PROD_CONFIG=/config_data/appsettings.Production.json +if [ ! -f $PROD_CONFIG ]; then + echo "{}" > $PROD_CONFIG +fi +ln -s $PROD_CONFIG /app/appsettings.Production.json -exec dotnet Tgstation.Server.Host.Console.dll "$@" +exec dotnet Tgstation.Server.Host.Console.dll diff --git a/docs/Doxyfile b/docs/Doxyfile index f5b2690326..21e213b32e 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -864,7 +864,7 @@ RECURSIVE = YES # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = packages +EXCLUDE = packages src/Tgstation.Server.Host/webroot src/Tgstation.Server.Host/ClientApp/node_modules src/Tgstation.Server.Host/ClientApp/build # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index b180057e3f..37dc08d241 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -107,6 +107,22 @@ var/commit //full sha of compiled commit var/origin_commit //full sha of last known remote commit. This may be null if the TGS repository is not currently tracking a remote branch +//represents a version of tgstation-server +/datum/tgs_version + var/suite //The suite version, can be >=3 + + //this group of variables can be null to represent a wild card + var/major //The major version + var/minor //The minor version + var/patch //The patch version + + var/raw_parameter //The unparsed parameter + var/deprefixed_parameter //The version only bit of raw_parameter + +//if the tgs_version is a wildcard version +/datum/tgs_version/proc/Wildcard() + return + //represents a merge of a GitHub pull request /datum/tgs_revision_information/test_merge var/number //pull request number @@ -155,22 +171,22 @@ //FUNCTIONS -//Returns the respective string version of the API +//Returns the respective supported /datum/tgs_version of the API /world/proc/TgsMaximumAPIVersion() return /world/proc/TgsMinimumAPIVersion() return -//Gets the current version of the server tools running the server -/world/proc/TgsVersion() - return - //Returns TRUE if the world was launched under the server tools and the API matches, FALSE otherwise //No function below this succeeds if it returns FALSE /world/proc/TgsAvailable() return +//Gets the current /datum/tgs_version of the server tools running the server +/world/proc/TgsVersion() + return + /world/proc/TgsInstanceName() return diff --git a/src/DMAPI/tgs/core/core.dm b/src/DMAPI/tgs/core/core.dm index f63cc84eea..609a8990e4 100644 --- a/src/DMAPI/tgs/core/core.dm +++ b/src/DMAPI/tgs/core/core.dm @@ -1,24 +1,46 @@ /world/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE) var/current_api = TGS_READ_GLOBAL(tgs) if(current_api) - TGS_ERROR_LOG("TgsNew(): TGS API datum already set ([current_api])!") + TGS_ERROR_LOG("TgsNew(): TGS API datum already set ([current_api])! Was TgsNew() called more than once?") return #ifdef TGS_V3_API minimum_required_security_level = TGS_SECURITY_TRUSTED #endif - - var/tgs_version = world.params[TGS_VERSION_PARAMETER] - if(!tgs_version) + var/raw_parameter = world.params[TGS_VERSION_PARAMETER] + if(!raw_parameter) return - var/path = SelectTgsApi(tgs_version) - if(!path) - TGS_ERROR_LOG("Found unsupported API version: [tgs_version]. If this is a valid version please report this, backporting is done on demand.") + var/datum/tgs_version/version = new(raw_parameter) + if(!version.Valid(FALSE)) + TGS_ERROR_LOG("Failed to validate TGS version parameter: [raw_parameter]!") return - TGS_INFO_LOG("Activating API for version [tgs_version]") - var/datum/tgs_api/new_api = new path + var/api_datum + switch(version.suite) + if(3) +#ifndef TGS_V3_API + TGS_ERROR_LOG("Detected V3 API but TGS_V3_API isn't defined!") +#else + switch(version.major) + if(2) + api_datum = /datum/tgs_api/v3210 +#endif + if(4) + switch(version.major) + if(0) + api_datum = /datum/tgs_api/v4 + + if(version.suite != null && version.major != null && version.minor != null && version.patch != null && version.deprefixed_parameter > TgsMaximumAPIVersion()) + TGS_ERROR_LOG("Detected unknown API version! Defaulting to latest. Update the DMAPI to fix this problem.") + api_datum = /datum/tgs_api/latest + + if(!api_datum) + TGS_ERROR_LOG("Found unsupported API version: [raw_parameter]. If this is a valid version please report this, backporting is done on demand.") + return + + TGS_INFO_LOG("Activating API for version [version.deprefixed_parameter]") + var/datum/tgs_api/new_api = new api_datum(version) TGS_WRITE_GLOBAL(tgs, new_api) @@ -27,40 +49,11 @@ TGS_WRITE_GLOBAL(tgs, null) TGS_ERROR_LOG("Failed to activate API!") -/world/proc/SelectTgsApi(tgs_version) - //remove the old 3.0 header - tgs_version = replacetext(tgs_version, "/tg/station 13 Server v", "") - - var/list/version_bits = splittext(tgs_version, ".") - - var/super = text2num(version_bits[1]) - var/major = text2num(version_bits[2]) - var/minor = text2num(version_bits[3]) - var/patch = text2num(version_bits[4]) - - switch(super) - if(3) -#ifndef TGS_V3_API - TGS_ERROR_LOG("Detected V3 API but TGS_V3_API isn't defined!") -#else - switch(major) - if(2) - return /datum/tgs_api/v3210 -#endif - if(4) - switch(major) - if(0) - return /datum/tgs_api/v4 - - if(super != null && major != null && minor != null && patch != null && tgs_version > TgsMaximumAPIVersion()) - TGS_ERROR_LOG("Detected unknown API version! Defaulting to latest. Update the DMAPI to fix this problem.") - return /datum/tgs_api/latest - /world/TgsMaximumAPIVersion() - return "4.0.0.0" + return new /datum/tgs_version("4.0.x.x") /world/TgsMinimumAPIVersion() - return "3.2.0.0" + return new /datum/tgs_version("3.2.0.0") /world/TgsInitializationComplete() var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) @@ -90,7 +83,9 @@ return TGS_READ_GLOBAL(tgs) != null /world/TgsVersion() - return world.params[TGS_VERSION_PARAMETER] + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + return api.version /world/TgsInstanceName() var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) diff --git a/src/DMAPI/tgs/core/datum.dm b/src/DMAPI/tgs/core/datum.dm index b2f9b19cdd..fb2508059a 100644 --- a/src/DMAPI/tgs/core/datum.dm +++ b/src/DMAPI/tgs/core/datum.dm @@ -1,6 +1,11 @@ TGS_DEFINE_AND_SET_GLOBAL(tgs, null) /datum/tgs_api + var/datum/tgs_version/version + +/datum/tgs_api/New(datum/tgs_version/version) + . = ..() + src.version = version /datum/tgs_api/latest parent_type = /datum/tgs_api/v4 diff --git a/src/DMAPI/tgs/core/tgs_version.dm b/src/DMAPI/tgs/core/tgs_version.dm new file mode 100644 index 0000000000..b97745611f --- /dev/null +++ b/src/DMAPI/tgs/core/tgs_version.dm @@ -0,0 +1,22 @@ +/datum/tgs_version/New(raw_parameter) + src.raw_parameter = raw_parameter + deprefixed_parameter = replacetext(raw_parameter, "/tg/station 13 Server v", "") + var/list/version_bits = splittext(deprefixed_parameter, ".") + + suite = text2num(version_bits[1]) + if(version_bits.len > 1) + major = text2num(version_bits[2]) + if(version_bits.len > 2) + minor = text2num(version_bits[3]) + if(version_bits.len == 4) + patch = text2num(version_bits[4]) + +/datum/tgs_version/proc/Valid(allow_wildcards = FALSE) + if(suite == null) + return FALSE + if(allow_wildcards) + return TRUE + return !Wildcard() + +/datum/tgs_version/Wildcard() + return major == null || minor == null || patch == null diff --git a/src/DMAPI/tgs/includes.dm b/src/DMAPI/tgs/includes.dm index a3f10b29f5..247f1fda5d 100644 --- a/src/DMAPI/tgs/includes.dm +++ b/src/DMAPI/tgs/includes.dm @@ -1,6 +1,7 @@ #include "core\_definitions.dm" #include "core\core.dm" #include "core\datum.dm" +#include "core\tgs_version.dm" #ifdef TGS_V3_API #include "v3210\api.dm" #include "v3210\commands.dm" diff --git a/src/Tgstation.Server.Api/ApiHeaders.cs b/src/Tgstation.Server.Api/ApiHeaders.cs index bf1c1cbb08..949fc5aaf4 100644 --- a/src/Tgstation.Server.Api/ApiHeaders.cs +++ b/src/Tgstation.Server.Api/ApiHeaders.cs @@ -1,12 +1,12 @@ -using System; +using Microsoft.AspNetCore.Http.Headers; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net.Http.Headers; using System.Reflection; -using Microsoft.AspNetCore.Http.Headers; -using Microsoft.Net.Http.Headers; -using Microsoft.Extensions.Primitives; namespace Tgstation.Server.Api { @@ -28,32 +28,32 @@ namespace Tgstation.Server.Api /// /// The header key /// - const string usernameHeader = "Username"; + const string UsernameHeader = "Username"; /// /// The header key /// - const string instanceIdHeader = "Instance"; + const string InstanceIdHeader = "Instance"; /// /// The JWT authentication header scheme /// - const string jwtAuthenticationScheme = "Bearer"; + const string JwtAuthenticationScheme = "Bearer"; /// /// The password authentication header scheme /// - const string passwordAuthenticationScheme = "Password"; + const string PasswordAuthenticationScheme = "Password"; /// - /// The current + /// The current /// - static readonly AssemblyName assemblyName = Assembly.GetExecutingAssembly().GetName(); + static readonly AssemblyName AssemblyName = Assembly.GetExecutingAssembly().GetName(); /// /// Get the version of the the caller is using /// - public static Version Version => assemblyName.Version; + public static Version Version => AssemblyName.Version; /// /// The being accessed @@ -139,12 +139,12 @@ namespace Tgstation.Server.Api if (!requestHeaders.Headers.TryGetValue(HeaderNames.UserAgent, out var userAgentValues) || !ProductInfoHeaderValue.TryParse(userAgentValues.FirstOrDefault(), out var clientUserAgent)) throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Missing {0} headers!", HeaderNames.UserAgent)); - //assure the client user agent has a name and version + // assure the client user agent has a name and version if (String.IsNullOrWhiteSpace(clientUserAgent.Product.Name) || !Version.TryParse(clientUserAgent.Product.Version, out var clientVersion)) throw new InvalidOperationException("Malformed client user agent!"); - //make sure the api header matches ours - if (!requestHeaders.Headers.TryGetValue(ApiVersionHeader, out var apiUserAgentHeaderValues) || !ProductInfoHeaderValue.TryParse(apiUserAgentHeaderValues.FirstOrDefault(), out var apiUserAgent) || apiUserAgent.Product.Name != assemblyName.Name) + // make sure the api header matches ours + if (!requestHeaders.Headers.TryGetValue(ApiVersionHeader, out var apiUserAgentHeaderValues) || !ProductInfoHeaderValue.TryParse(apiUserAgentHeaderValues.FirstOrDefault(), out var apiUserAgent) || apiUserAgent.Product.Name != AssemblyName.Name) throw new InvalidOperationException("Missing API version!"); if (!Version.TryParse(apiUserAgent.Product.Version, out var apiVersion)) @@ -166,7 +166,7 @@ namespace Tgstation.Server.Api if (String.IsNullOrEmpty(parameter)) throw new InvalidOperationException("Missing authentication parameter!"); - if (requestHeaders.Headers.TryGetValue(instanceIdHeader, out var instanceIdValues)) + if (requestHeaders.Headers.TryGetValue(InstanceIdHeader, out var instanceIdValues)) { var instanceIdString = instanceIdValues.FirstOrDefault(); if (instanceIdString != default && Int64.TryParse(instanceIdString, out var instanceId)) @@ -175,17 +175,18 @@ namespace Tgstation.Server.Api switch (scheme) { - case jwtAuthenticationScheme: + case JwtAuthenticationScheme: Token = parameter; break; - case passwordAuthenticationScheme: + case PasswordAuthenticationScheme: Password = parameter; - var fail = !requestHeaders.Headers.TryGetValue(usernameHeader, out var values); + var fail = !requestHeaders.Headers.TryGetValue(UsernameHeader, out var values); if (!fail) { Username = values.FirstOrDefault(); fail = String.IsNullOrWhiteSpace(Username); } + if (fail) throw new InvalidOperationException("Missing Username header!"); break; @@ -231,17 +232,18 @@ namespace Tgstation.Server.Api headers.Clear(); headers.Accept.Add(new MediaTypeWithQualityHeaderValue(ApplicationJson)); if (IsTokenAuthentication) - headers.Authorization = new AuthenticationHeaderValue(jwtAuthenticationScheme, Token); + headers.Authorization = new AuthenticationHeaderValue(JwtAuthenticationScheme, Token); else { - headers.Authorization = new AuthenticationHeaderValue(passwordAuthenticationScheme, Password); - headers.Add(usernameHeader, Username); + headers.Authorization = new AuthenticationHeaderValue(PasswordAuthenticationScheme, Password); + headers.Add(UsernameHeader, Username); } + headers.UserAgent.Add(new ProductInfoHeaderValue(UserAgent)); - headers.Add(ApiVersionHeader, new ProductHeaderValue(assemblyName.Name, ApiVersion.ToString()).ToString()); + headers.Add(ApiVersionHeader, new ProductHeaderValue(AssemblyName.Name, ApiVersion.ToString()).ToString()); instanceId = instanceId ?? InstanceId; if (instanceId.HasValue) - headers.Add(instanceIdHeader, instanceId.ToString()); + headers.Add(InstanceIdHeader, instanceId.ToString()); } } } diff --git a/src/Tgstation.Server.Api/Models/Administration.cs b/src/Tgstation.Server.Api/Models/Administration.cs index 71eaba974b..01c0dcf9ac 100644 --- a/src/Tgstation.Server.Api/Models/Administration.cs +++ b/src/Tgstation.Server.Api/Models/Administration.cs @@ -2,7 +2,9 @@ namespace Tgstation.Server.Api.Models { - /// + /// + /// Represents administrative server information + /// public sealed class Administration { /// diff --git a/src/Tgstation.Server.Api/Models/ChatProvider.cs b/src/Tgstation.Server.Api/Models/ChatProvider.cs index f41d9de9c5..4fb3e90902 100644 --- a/src/Tgstation.Server.Api/Models/ChatProvider.cs +++ b/src/Tgstation.Server.Api/Models/ChatProvider.cs @@ -9,6 +9,7 @@ /// Internet relay chat /// Irc, + /// /// Superior chat service /// diff --git a/src/Tgstation.Server.Api/Models/ConfigurationFile.cs b/src/Tgstation.Server.Api/Models/ConfigurationFile.cs index bbacc2e3ba..cddaa5eecf 100644 --- a/src/Tgstation.Server.Api/Models/ConfigurationFile.cs +++ b/src/Tgstation.Server.Api/Models/ConfigurationFile.cs @@ -28,6 +28,8 @@ /// /// The content of the . Will be if is or during listing and write operations /// +#pragma warning disable CA1819 // Properties should not return arrays public byte[] Content { get; set; } +#pragma warning restore CA1819 // Properties should not return arrays } } diff --git a/src/Tgstation.Server.Api/Models/ConfigurationType.cs b/src/Tgstation.Server.Api/Models/ConfigurationType.cs index d322e79a75..350af0c36f 100644 --- a/src/Tgstation.Server.Api/Models/ConfigurationType.cs +++ b/src/Tgstation.Server.Api/Models/ConfigurationType.cs @@ -9,10 +9,12 @@ /// Configuration editing is not allowed /// Disallowed, + /// /// Configuration editing is allowed by all users on all files /// HostWrite, + /// /// Co /// diff --git a/src/Tgstation.Server.Api/Models/DreamDaemonSecurity.cs b/src/Tgstation.Server.Api/Models/DreamDaemonSecurity.cs index 68804af41b..8f6b1f0f26 100644 --- a/src/Tgstation.Server.Api/Models/DreamDaemonSecurity.cs +++ b/src/Tgstation.Server.Api/Models/DreamDaemonSecurity.cs @@ -9,10 +9,12 @@ /// Server is unrestricted in terms of file access and shell commands /// Trusted, + /// /// Server will not be able to run shell commands or access files outside it's working directory /// Safe, + /// /// Server will not be able to run shell commands or access anything but temporary files. Currently unsupported! /// diff --git a/src/Tgstation.Server.Api/Models/DreamDaemonStatus.cs b/src/Tgstation.Server.Api/Models/DreamDaemonStatus.cs index a09c75d399..413a3e8586 100644 --- a/src/Tgstation.Server.Api/Models/DreamDaemonStatus.cs +++ b/src/Tgstation.Server.Api/Models/DreamDaemonStatus.cs @@ -11,10 +11,12 @@ /// Server is not running /// Offline, + /// /// Server is being rebooted /// HardRebooting, + /// /// Server is running /// diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs index 0349ce9fce..e7afce2eaa 100644 --- a/src/Tgstation.Server.Api/Models/Instance.cs +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -50,7 +50,10 @@ namespace Tgstation.Server.Api.Models [NotMapped] public Job MoveJob { get; set; } - /// + /// + /// Create a clone of the essential metadata + /// + /// A clone of the essential metadata public Instance CloneMetadata() => new Instance { Id = Id, diff --git a/src/Tgstation.Server.Api/Models/Internal/ChatConnectionStringBuilder.cs b/src/Tgstation.Server.Api/Models/Internal/ChatConnectionStringBuilder.cs index b5d3b5f9a3..51d8025f73 100644 --- a/src/Tgstation.Server.Api/Models/Internal/ChatConnectionStringBuilder.cs +++ b/src/Tgstation.Server.Api/Models/Internal/ChatConnectionStringBuilder.cs @@ -13,7 +13,7 @@ /// /// Gets the associated with the /// - /// + /// The associated with the public abstract override string ToString(); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs b/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs index e1331a1029..312f5812d1 100644 --- a/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs +++ b/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs @@ -10,13 +10,15 @@ namespace Tgstation.Server.Api.Models.Internal /// /// The revision sha /// - [Required, StringLength(40)] + [Required] + [StringLength(40)] public string CommitSha { get; set; } /// /// The sha of the most recent remote commit /// - [Required, StringLength(40)] + [Required] + [StringLength(40)] public string OriginCommitSha { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Internal/TestMergeBase.cs b/src/Tgstation.Server.Api/Models/Internal/TestMergeBase.cs index b0bbb0db23..76dbe54ce2 100644 --- a/src/Tgstation.Server.Api/Models/Internal/TestMergeBase.cs +++ b/src/Tgstation.Server.Api/Models/Internal/TestMergeBase.cs @@ -42,7 +42,7 @@ namespace Tgstation.Server.Api.Models.Internal /// /// Construct a from a /// - /// + /// The to copy data from protected TestMergeBase(TestMergeBase copy) { if (copy == null) diff --git a/src/Tgstation.Server.Api/Models/IrcConnectionStringBuilder.cs b/src/Tgstation.Server.Api/Models/IrcConnectionStringBuilder.cs index 55df172e51..8f4534f0c5 100644 --- a/src/Tgstation.Server.Api/Models/IrcConnectionStringBuilder.cs +++ b/src/Tgstation.Server.Api/Models/IrcConnectionStringBuilder.cs @@ -87,6 +87,8 @@ namespace Tgstation.Server.Api.Models case IrcPasswordType.Server: PasswordType = passwordType; break; + default: + break; } if (splits.Length < 6) @@ -116,6 +118,7 @@ namespace Tgstation.Server.Api.Models sb.Append(';'); sb.Append(Password); } + return sb.ToString(); } } diff --git a/src/Tgstation.Server.Api/Models/IrcPasswordType.cs b/src/Tgstation.Server.Api/Models/IrcPasswordType.cs index bb3451bfbd..4c612f618a 100644 --- a/src/Tgstation.Server.Api/Models/IrcPasswordType.cs +++ b/src/Tgstation.Server.Api/Models/IrcPasswordType.cs @@ -9,10 +9,12 @@ /// Use server authentication /// Server, + /// /// Use PLAIN sasl authentication /// Sasl, + /// /// Use NickServ authentication /// diff --git a/src/Tgstation.Server.Api/Rights/AdministrationRights.cs b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs index 84c93c4cb3..1d175b25db 100644 --- a/src/Tgstation.Server.Api/Rights/AdministrationRights.cs +++ b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs @@ -12,22 +12,27 @@ namespace Tgstation.Server.Api.Rights /// User has no rights /// None = 0, + /// /// User can edit themself and other s and also create others /// WriteUsers = 1, + /// /// User can gracefully restart the host /// RestartHost = 2, + /// /// User can change /// ChangeVersion = 4, + /// /// User can change their password /// EditOwnPassword = 8, + /// /// User can read info and rights of other users /// diff --git a/src/Tgstation.Server.Api/Rights/ByondRights.cs b/src/Tgstation.Server.Api/Rights/ByondRights.cs index 4cf795d352..f6642164c7 100644 --- a/src/Tgstation.Server.Api/Rights/ByondRights.cs +++ b/src/Tgstation.Server.Api/Rights/ByondRights.cs @@ -12,18 +12,22 @@ namespace Tgstation.Server.Api.Rights /// User has no rights /// None = 0, + /// /// User may check the active installed BYOND version /// ReadActive = 1, + /// /// User may list all installed BYOND versions /// ListInstalled = 2, + /// /// User may change the active BYOND version /// ChangeVersion = 4, + /// /// User may cancel version installations /// diff --git a/src/Tgstation.Server.Api/Rights/ChatBotRights.cs b/src/Tgstation.Server.Api/Rights/ChatBotRights.cs index b993335c3c..11572bb0b1 100644 --- a/src/Tgstation.Server.Api/Rights/ChatBotRights.cs +++ b/src/Tgstation.Server.Api/Rights/ChatBotRights.cs @@ -12,38 +12,47 @@ namespace Tgstation.Server.Api.Rights /// User has no rights /// None = 0, + /// /// User can change /// WriteEnabled = 1, + /// /// User can change /// WriteProvider = 2, + /// /// User can change /// WriteChannels = 4, + /// /// User can change /// WriteConnectionString = 8, + /// /// User can read requires /// ReadConnectionString = 16, + /// /// User can read all chat settings except /// Read = 32, + /// /// User can create new /// Create = 64, + /// /// User can delete /// Delete = 128, + /// /// User can change /// diff --git a/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs b/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs index c05a40923b..774fa68bd9 100644 --- a/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs +++ b/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs @@ -12,18 +12,22 @@ namespace Tgstation.Server.Api.Rights /// User has no rights /// None = 0, + /// /// User may read files /// Read = 1, + /// /// User may write files /// Write = 2, + /// /// User may list files /// List = 4, + /// /// User may delete empty folders /// diff --git a/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs b/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs index b9a950b3f0..fc07682e1b 100644 --- a/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs +++ b/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs @@ -12,50 +12,62 @@ namespace Tgstation.Server.Api.Rights /// User has no rights /// None = 0, + /// /// User can read and /// ReadRevision = 1, + /// /// User can change both primary and secondary ports /// SetPorts = 2, + /// /// User can change /// SetAutoStart = 4, + /// /// User set /// SetSecurity = 8, + /// /// User can read all ports, , , , , and /// ReadMetadata = 16, + /// /// User can change /// SetWebClient = 32, + /// /// User can enable /// SoftRestart = 64, + /// /// User can enable /// SoftShutdown = 128, + /// /// User can immediately restart /// Restart = 256, + /// /// User can immediately shutdown /// Shutdown = 512, + /// /// User can start and disable and /// Start = 1024, + /// /// User can change /// diff --git a/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs index e9b04645f4..e92f6bfcd3 100644 --- a/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs +++ b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs @@ -12,30 +12,37 @@ namespace Tgstation.Server.Api.Rights /// User has no rights /// None = 0, + /// /// User may read status /// Read = 1, + /// /// User may trigger compiles /// Compile = 2, + /// /// User may cancel compiles /// CancelCompile = 4, + /// /// User may modify /// SetDme = 8, + /// /// User may modify /// SetApiValidationPort = 16, + /// /// User may list and read all s /// CompileJobs = 32, + /// /// User may modify /// diff --git a/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs index 552af89b45..1961b81d13 100644 --- a/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs +++ b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs @@ -12,38 +12,47 @@ namespace Tgstation.Server.Api.Rights /// User has no rights /// None = 0, + /// /// User can view s which they have any rights for /// Read = 1, + /// /// User can create s /// Create = 2, + /// /// User can rename s they can view /// Rename = 4, + /// /// User can relocate s they can view /// Relocate = 8, + /// /// User can online s they can view /// SetOnline = 16, + /// /// User can delete s they can view /// Delete = 32, + /// /// User can view all s /// List = 64, + /// /// User can change /// SetConfiguration = 128, + /// /// User can change /// diff --git a/src/Tgstation.Server.Api/Rights/InstanceUserRights.cs b/src/Tgstation.Server.Api/Rights/InstanceUserRights.cs index b3860cc36d..a32ee62016 100644 --- a/src/Tgstation.Server.Api/Rights/InstanceUserRights.cs +++ b/src/Tgstation.Server.Api/Rights/InstanceUserRights.cs @@ -12,14 +12,17 @@ namespace Tgstation.Server.Api.Rights /// User has no rights /// None = 0, + /// /// Allow read access to for the /// ReadUsers = 1, + /// /// Allow write and delete access to for the /// WriteUsers = 2, + /// /// Allow adding additional to the /// diff --git a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs index 8c5753d464..094f555e39 100644 --- a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs +++ b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs @@ -12,54 +12,67 @@ namespace Tgstation.Server.Api.Rights /// User has no rights /// None = 0, + /// /// User may cancel synchronize operations /// CancelPendingChanges = 1, + /// /// User may create the if it does not exist /// SetOrigin = 2, + /// /// User may directly set the sha the 's HEAD points to /// SetSha = 4, + /// /// User may fetch and merge GitHub pull requests /// MergePullRequest = 8, + /// /// User may use the update feature /// UpdateBranch = 16, + /// /// User may change and /// ChangeCommitter = 32, + /// /// User may change and /// ChangeTestMergeCommits = 64, + /// /// User may read and change and /// ChangeCredentials = 128, + /// /// User may set to another git reference (not a SHA) /// SetReference = 256, + /// /// User may read all fields in the with the exception of /// Read = 512, + /// /// User may change and /// ChangeAutoUpdateSettings = 1024, + /// /// User may delete the /// Delete = 2048, + /// /// User may cancel clone operations /// diff --git a/src/Tgstation.Server.Api/Rights/RightsHelper.cs b/src/Tgstation.Server.Api/Rights/RightsHelper.cs index bf062d81e7..6df923331e 100644 --- a/src/Tgstation.Server.Api/Rights/RightsHelper.cs +++ b/src/Tgstation.Server.Api/Rights/RightsHelper.cs @@ -12,11 +12,10 @@ namespace Tgstation.Server.Api.Rights /// /// Map of s to their respective flag s /// - static readonly IReadOnlyDictionary typeMap = new Dictionary + static readonly IReadOnlyDictionary TypeMap = new Dictionary { { RightsType.Administration, typeof(AdministrationRights) }, { RightsType.InstanceManager, typeof(InstanceManagerRights) }, - { RightsType.Repository, typeof(RepositoryRights) }, { RightsType.Byond, typeof(ByondRights) }, { RightsType.DreamMaker, typeof(DreamMakerRights) }, @@ -31,7 +30,7 @@ namespace Tgstation.Server.Api.Rights /// /// The to lookup /// The of the given - public static Type RightToType(RightsType rightsType) => typeMap[rightsType]; + public static Type RightToType(RightsType rightsType) => TypeMap[rightsType]; /// /// Gets the role claim name used for a given @@ -47,7 +46,8 @@ namespace Tgstation.Server.Api.Rights foreach (Enum J in Enum.GetValues(right.GetType())) if (Convert.ToInt32(J, CultureInfo.InvariantCulture) != 0 && right.HasFlag(J)) yield return String.Concat(typeof(TRight).Name, '.', J.ToString()); - }; + } + var names = GetRoleNames(); return String.Join(",", names); } @@ -60,7 +60,7 @@ namespace Tgstation.Server.Api.Rights /// A representing the claim role name public static string RoleName(RightsType rightsType, Enum right) { - var enumType = typeMap[rightsType]; + var enumType = TypeMap[rightsType]; return String.Concat(enumType.Name, '.', Enum.GetName(enumType, right)); } diff --git a/src/Tgstation.Server.Api/Rights/RightsType.cs b/src/Tgstation.Server.Api/Rights/RightsType.cs index 5ebd1948e3..a541c5048f 100644 --- a/src/Tgstation.Server.Api/Rights/RightsType.cs +++ b/src/Tgstation.Server.Api/Rights/RightsType.cs @@ -9,34 +9,42 @@ /// /// Administration, + /// /// /// InstanceManager, + /// /// /// Repository, + /// /// /// Byond, + /// /// /// DreamMaker, + /// /// /// DreamDaemon, + /// /// /// ChatBots, + /// /// /// Configuration, + /// /// /// diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index 16421b854f..dbd49b3b43 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -16,19 +16,20 @@ json web api tgstation-server tgstation ss13 byond Initial release 4.0.1.0 + ../../build/analyzers.ruleset + latest - latest true bin\Release\netstandard2.0\Tgstation.Server.Api.xml - 1701;1702;1705;CA2227;CA1819;CA1028 + 1701;1702;CA1028 - latest - 1701;1702;1705;CA2227;CA1819;CA1028 + 1701;1702;CA1028 + bin\Debug\netstandard2.0\Tgstation.Server.Api.xml @@ -37,7 +38,14 @@ all compile; build; native; contentfiles; analyzers + + all + runtime; build; native; contentfiles; analyzers + + + + diff --git a/src/Tgstation.Server.Client/ApiClient.cs b/src/Tgstation.Server.Client/ApiClient.cs index 452bcf1e23..3d01a67871 100644 --- a/src/Tgstation.Server.Client/ApiClient.cs +++ b/src/Tgstation.Server.Client/ApiClient.cs @@ -49,6 +49,79 @@ namespace Tgstation.Server.Client /// ApiHeaders headers; + static JsonSerializerSettings GetSerializerSettings() => new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + Converters = new[] { new VersionConverter() } + }; + + static void HandleBadResponse(HttpResponseMessage response, string json) + { + ErrorMessage errorMessage = null; + try + { + // check if json serializes to an error message + errorMessage = JsonConvert.DeserializeObject(json, GetSerializerSettings()); + } + catch (JsonException) { } + + const string BadSpecExtension = " This is not part of TGS4 communication specification and should be reported if it was returned from a TGS4 server!"; + +#pragma warning disable IDE0010 // Add missing cases + switch (response.StatusCode) +#pragma warning restore IDE0010 // Add missing cases + { + case HttpStatusCode.UpgradeRequired: + throw new ApiMismatchException(errorMessage ?? new ErrorMessage + { + Message = "API Mismatch but no current API version provided!" + BadSpecExtension, + SeverApiVersion = null + }); + case HttpStatusCode.Unauthorized: + throw new UnauthorizedException(); + case HttpStatusCode.RequestTimeout: + throw new RequestTimeoutException(); + case HttpStatusCode.Forbidden: + throw new InsufficientPermissionsException(); + case HttpStatusCode.ServiceUnavailable: + throw new ServiceUnavailableException(); + case HttpStatusCode.Gone: + errorMessage = errorMessage ?? new ErrorMessage + { + Message = "The requested resource could not be found!", + SeverApiVersion = null + }; + goto case HttpStatusCode.Conflict; + case HttpStatusCode.NotFound: + // our fault somehow + errorMessage = errorMessage ?? new ErrorMessage + { + Message = "This is not a valid route!" + BadSpecExtension, + SeverApiVersion = null + }; + goto case HttpStatusCode.Conflict; + case HttpStatusCode.Conflict: + throw new ConflictException(errorMessage ?? new ErrorMessage + { + Message = "An undescribed conflict occurred!" + BadSpecExtension, + SeverApiVersion = null + }, response.StatusCode); + case HttpStatusCode.NotImplemented: + // unprocessable entity + case (HttpStatusCode)422: + throw new MethodNotSupportedException(); + case HttpStatusCode.InternalServerError: + // response json is html + throw new ServerErrorException(json); + case (HttpStatusCode)429: + // rate limited + response.Headers.TryGetValues("Retry-After", out var values); + throw new RateLimitException(values?.FirstOrDefault()); + default: + throw new ApiConflictException(errorMessage, response.StatusCode); + } + } + /// /// Construct an /// @@ -60,7 +133,7 @@ namespace Tgstation.Server.Client this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); Url = url ?? throw new ArgumentNullException(nameof(url)); headers = apiHeaders ?? throw new ArgumentNullException(nameof(apiHeaders)); - + requestLoggers = new List(); } @@ -70,10 +143,11 @@ namespace Tgstation.Server.Client /// /// Main request method /// + /// The resulting POCO type /// The route to run /// The body of the request /// The method of the request - /// The optional for the request + /// The optional for the request /// The for the operation /// A resulting in the response on success async Task RunRequest(string route, object body, HttpMethod method, long? instanceId, CancellationToken cancellationToken) @@ -85,102 +159,42 @@ namespace Tgstation.Server.Client if (body == null && (method == HttpMethod.Post || method == HttpMethod.Put)) throw new InvalidOperationException("Body cannot be null for POST or PUT!"); + HttpResponseMessage response; var fullUri = new Uri(Url, route); - - var message = new HttpRequestMessage(method, fullUri); - - var serializerSettings = new JsonSerializerSettings + var serializerSettings = GetSerializerSettings(); + using (var request = new HttpRequestMessage(method, fullUri)) { - ContractResolver = new CamelCasePropertyNamesContractResolver(), - Converters = new[] { new VersionConverter() } - }; + if (body != null) + request.Content = new StringContent(JsonConvert.SerializeObject(body, serializerSettings), Encoding.UTF8, ApiHeaders.ApplicationJson); - if (body != null) - message.Content = new StringContent(JsonConvert.SerializeObject(body, serializerSettings), Encoding.UTF8, ApiHeaders.ApplicationJson); + headers.SetRequestHeaders(request.Headers, instanceId); - headers.SetRequestHeaders(message.Headers, instanceId); + await Task.WhenAll(requestLoggers.Select(x => x.LogRequest(request, cancellationToken))).ConfigureAwait(false); - await Task.WhenAll(requestLoggers.Select(x => x.LogRequest(message, cancellationToken))).ConfigureAwait(false); + response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); + } - var response = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); - - await Task.WhenAll(requestLoggers.Select(x => x.LogResponse(response, cancellationToken))).ConfigureAwait(false); - - var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - - if (!response.IsSuccessStatusCode) + using (response) { - ErrorMessage errorMessage = null; + await Task.WhenAll(requestLoggers.Select(x => x.LogResponse(response, cancellationToken))).ConfigureAwait(false); + + var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + + if (!response.IsSuccessStatusCode) + HandleBadResponse(response, json); + + if (String.IsNullOrWhiteSpace(json)) + json = JsonConvert.SerializeObject(new object()); + try { - //check if json serializes to an error message - errorMessage = JsonConvert.DeserializeObject(json, serializerSettings); + return JsonConvert.DeserializeObject(json, serializerSettings); } - catch (JsonException) { } - - const string BadSpecExtension = " This is not part of TGS4 communication specification and should be reported if it was returned from a TGS4 server!"; - - switch (response.StatusCode) + catch (JsonException) { - case HttpStatusCode.UpgradeRequired: - throw new ApiMismatchException(errorMessage ?? new ErrorMessage - { - Message = "API Mismatch but no current API version provided!" + BadSpecExtension, - SeverApiVersion = null - }); - case HttpStatusCode.Unauthorized: - throw new UnauthorizedException(); - case HttpStatusCode.RequestTimeout: - throw new RequestTimeoutException(); - case HttpStatusCode.Forbidden: - throw new InsufficientPermissionsException(); - case HttpStatusCode.ServiceUnavailable: - throw new ServiceUnavailableException(); - case HttpStatusCode.Gone: - errorMessage = errorMessage ?? new ErrorMessage - { - Message = "The requested resource could not be found!", - SeverApiVersion = null - }; - goto case HttpStatusCode.Conflict; - case HttpStatusCode.NotFound: //our fault somehow - errorMessage = errorMessage ?? new ErrorMessage - { - Message = "This is not a valid route!" + BadSpecExtension, - SeverApiVersion = null - }; - goto case HttpStatusCode.Conflict; - case HttpStatusCode.Conflict: - throw new ConflictException(errorMessage ?? new ErrorMessage - { - Message = "An undescribed conflict occurred!" + BadSpecExtension, - SeverApiVersion = null - }, response.StatusCode); - case HttpStatusCode.NotImplemented: - case (HttpStatusCode)422: //unprocessable entity - throw new MethodNotSupportedException(); - case HttpStatusCode.InternalServerError: - //response - throw new ServerErrorException(json); //json is html - case (HttpStatusCode)429: //rate limited - response.Headers.TryGetValues("Retry-After", out var values); - throw new RateLimitException(values?.FirstOrDefault()); - default: - throw new ApiConflictException(errorMessage, response.StatusCode); + throw new UnrecognizedResponseException(json, response.StatusCode); } } - - if (String.IsNullOrWhiteSpace(json)) - json = JsonConvert.SerializeObject(new object()); - - try - { - return JsonConvert.DeserializeObject(json, serializerSettings); - } - catch (JsonException) - { - throw new UnrecognizedResponseException(json, response.StatusCode); - } } /// diff --git a/src/Tgstation.Server.Client/Components/ByondClient.cs b/src/Tgstation.Server.Client/Components/ByondClient.cs index 92c2f8aba5..f3675dc265 100644 --- a/src/Tgstation.Server.Client/Components/ByondClient.cs +++ b/src/Tgstation.Server.Client/Components/ByondClient.cs @@ -14,6 +14,7 @@ namespace Tgstation.Server.Client.Components /// The for the /// readonly IApiClient apiClient; + /// /// The for the /// diff --git a/src/Tgstation.Server.Client/Components/ChatBotsClient.cs b/src/Tgstation.Server.Client/Components/ChatBotsClient.cs index 5c303f5f77..f7890f1f22 100644 --- a/src/Tgstation.Server.Client/Components/ChatBotsClient.cs +++ b/src/Tgstation.Server.Client/Components/ChatBotsClient.cs @@ -14,6 +14,7 @@ namespace Tgstation.Server.Client.Components /// The for the /// readonly IApiClient apiClient; + /// /// The for the /// diff --git a/src/Tgstation.Server.Client/Components/ConfigurationClient.cs b/src/Tgstation.Server.Client/Components/ConfigurationClient.cs index 1f1af74469..54dc6acf39 100644 --- a/src/Tgstation.Server.Client/Components/ConfigurationClient.cs +++ b/src/Tgstation.Server.Client/Components/ConfigurationClient.cs @@ -14,6 +14,7 @@ namespace Tgstation.Server.Client.Components /// The for the /// readonly IApiClient apiClient; + /// /// The for the /// diff --git a/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs b/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs index e077ea142d..6c2d5869d2 100644 --- a/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs +++ b/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs @@ -13,6 +13,7 @@ namespace Tgstation.Server.Client.Components /// The for the /// readonly IApiClient apiClient; + /// /// The for the /// diff --git a/src/Tgstation.Server.Client/Components/InstanceUserClient.cs b/src/Tgstation.Server.Client/Components/InstanceUserClient.cs index c3ceeca108..440340b6c4 100644 --- a/src/Tgstation.Server.Client/Components/InstanceUserClient.cs +++ b/src/Tgstation.Server.Client/Components/InstanceUserClient.cs @@ -14,6 +14,7 @@ namespace Tgstation.Server.Client.Components /// The for the /// readonly IApiClient apiClient; + /// /// The for the /// @@ -33,6 +34,7 @@ namespace Tgstation.Server.Client.Components /// public Task Create(InstanceUser instanceUser, CancellationToken cancellationToken) => apiClient.Create(Routes.InstanceUser, instanceUser ?? throw new ArgumentNullException(nameof(instanceUser)), instance.Id, cancellationToken); + /// public Task Delete(InstanceUser instanceUser, CancellationToken cancellationToken) => apiClient.Delete(Routes.SetID(Routes.InstanceUser, instanceUser.UserId.Value), instance.Id, cancellationToken); /// diff --git a/src/Tgstation.Server.Client/Components/JobsClient.cs b/src/Tgstation.Server.Client/Components/JobsClient.cs index 59cdbc22ba..d6c421e65d 100644 --- a/src/Tgstation.Server.Client/Components/JobsClient.cs +++ b/src/Tgstation.Server.Client/Components/JobsClient.cs @@ -14,6 +14,7 @@ namespace Tgstation.Server.Client.Components /// The for the /// readonly IApiClient apiClient; + /// /// The for the /// diff --git a/src/Tgstation.Server.Client/Components/RepositoryClient.cs b/src/Tgstation.Server.Client/Components/RepositoryClient.cs index 18e332fd09..97d4cdfec5 100644 --- a/src/Tgstation.Server.Client/Components/RepositoryClient.cs +++ b/src/Tgstation.Server.Client/Components/RepositoryClient.cs @@ -13,6 +13,7 @@ namespace Tgstation.Server.Client.Components /// The for the /// readonly IApiClient apiClient; + /// /// The for the /// @@ -22,7 +23,7 @@ namespace Tgstation.Server.Client.Components /// Construct a /// /// The value of - /// + /// The value of public RepositoryClient(IApiClient apiClient, Instance instance) { this.apiClient = apiClient; diff --git a/src/Tgstation.Server.Client/IApiClient.cs b/src/Tgstation.Server.Client/IApiClient.cs index 7eb8538156..ab833b1731 100644 --- a/src/Tgstation.Server.Client/IApiClient.cs +++ b/src/Tgstation.Server.Client/IApiClient.cs @@ -10,10 +10,19 @@ namespace Tgstation.Server.Client /// interface IApiClient : IDisposable { + /// + /// The the uses + /// ApiHeaders Headers { get; set; } + /// + /// The pointing the tgstation-server + /// Uri Url { get; } + /// + /// The request timeout + /// TimeSpan Timeout { get; set; } /// @@ -22,21 +31,155 @@ namespace Tgstation.Server.Client /// The to add void AddRequestLogger(IRequestLogger requestLogger); + /// + /// Run an HTTP PUT request + /// + /// The type to of the request body + /// The type of the response body + /// The server route to make the request to + /// The request body + /// The for the operation + /// A resulting in the response body as a Task Create(string route, TBody body, CancellationToken cancellationToken); + + /// + /// Run an HTTP PUT request + /// + /// The type of the response body + /// The server route to make the request to + /// The for the operation + /// A resulting in the response body as a Task Create(string route, CancellationToken cancellationToken); + + /// + /// Run an HTTP GET request + /// + /// The type of the response body + /// The server route to make the request to + /// The for the operation + /// A resulting in the response body as a Task Read(string route, CancellationToken cancellationToken); + + /// + /// Run an HTTP POST request + /// + /// The type to of the request body + /// The type of the response body + /// The server route to make the request to + /// The request body + /// The for the operation + /// A resulting in the response body as a Task Update(string route, TBody body, CancellationToken cancellationToken); + + /// + /// Run an HTTP POST request + /// + /// The type of the response body + /// The server route to make the request to + /// The for the operation + /// A resulting in the response body as a Task Update(string route, CancellationToken cancellationToken); + + /// + /// Run an HTTP POST request + /// + /// The type to of the request body + /// The server route to make the request to + /// The request body + /// The for the operation + /// A representing the running operation Task Update(string route, TBody body, CancellationToken cancellationToken); + + /// + /// Run an HTTP DELETE request + /// + /// The server route to make the request to + /// The for the operation + /// A representing the running operation Task Delete(string route, CancellationToken cancellationToken); + /// + /// Run an HTTP PUT request + /// + /// The type to of the request body + /// The type of the response body + /// The server route to make the request to + /// The request body + /// The to make the request to + /// The for the operation + /// A resulting in the response body as a Task Create(string route, TBody body, long instanceId, CancellationToken cancellationToken); + + /// + /// Run an HTTP PUT request + /// + /// The type of the response body + /// The server route to make the request to + /// The to make the request to + /// The for the operation + /// A resulting in the response body as a Task Create(string route, long instanceId, CancellationToken cancellationToken); + + /// + /// Run an HTTP PATCH request + /// + /// The type of the response body + /// The server route to make the request to + /// The to make the request to + /// The for the operation + /// A resulting in the response body as a Task Patch(string route, long instanceId, CancellationToken cancellationToken); + + /// + /// Run an HTTP GET request + /// + /// The type of the response body + /// The server route to make the request to + /// The to make the request to + /// The for the operation + /// A resulting in the response body as a Task Read(string route, long instanceId, CancellationToken cancellationToken); + + /// + /// Run an HTTP POST request + /// + /// The type to of the request body + /// The type of the response body + /// The server route to make the request to + /// The request body + /// The to make the request to + /// The for the operation + /// A resulting in the response body as a Task Update(string route, TBody body, long instanceId, CancellationToken cancellationToken); + + /// + /// Run an HTTP DELETE request + /// + /// The server route to make the request to + /// The to make the request to + /// The for the operation + /// A representing the running operation Task Delete(string route, long instanceId, CancellationToken cancellationToken); + + /// + /// Run an HTTP DELETE request + /// + /// The type to of the request body + /// The server route to make the request to + /// The request body + /// The to make the request to + /// The for the operation + /// A representing the running operation Task Delete(string route, TBody body, long instanceId, CancellationToken cancellationToken); + + /// + /// Run an HTTP DELETE request + /// + /// The type of the response body + /// The server route to make the request to + /// The to make the request to + /// The for the operation + /// A resulting in the response body as a Task Delete(string route, long instanceId, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Client/IServerClient.cs b/src/Tgstation.Server.Client/IServerClient.cs index 9432ea9afd..685606df7d 100644 --- a/src/Tgstation.Server.Client/IServerClient.cs +++ b/src/Tgstation.Server.Client/IServerClient.cs @@ -43,6 +43,8 @@ namespace Tgstation.Server.Client /// /// The of the /// + /// The for the operation + /// A resulting in the of the target server Task Version(CancellationToken cancellationToken); /// diff --git a/src/Tgstation.Server.Client/IServerClientFactory.cs b/src/Tgstation.Server.Client/IServerClientFactory.cs index df0427efef..32816de376 100644 --- a/src/Tgstation.Server.Client/IServerClientFactory.cs +++ b/src/Tgstation.Server.Client/IServerClientFactory.cs @@ -16,8 +16,8 @@ namespace Tgstation.Server.Client /// The URL to access TGS /// The username to for the /// The password for the - /// The for the operation /// The representing timeout for the connection + /// The for the operation /// A resulting in a new Task CreateServerClient(Uri host, string username, string password, TimeSpan timeout = default, CancellationToken cancellationToken = default); diff --git a/src/Tgstation.Server.Client/InstanceManagerClient.cs b/src/Tgstation.Server.Client/InstanceManagerClient.cs index 0aad027e0b..62d99de98d 100644 --- a/src/Tgstation.Server.Client/InstanceManagerClient.cs +++ b/src/Tgstation.Server.Client/InstanceManagerClient.cs @@ -24,7 +24,7 @@ namespace Tgstation.Server.Client /// /// Construct an /// - /// + /// The value of public InstanceManagerClient(IApiClient apiClient) { this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient)); @@ -55,6 +55,7 @@ namespace Tgstation.Server.Client client = new InstanceClient(apiClient, instance); cachedClients.Add(instance.Id, client); } + return client; } } diff --git a/src/Tgstation.Server.Client/ServerClientFactory.cs b/src/Tgstation.Server.Client/ServerClientFactory.cs index b0a7d3b547..62eec5644c 100644 --- a/src/Tgstation.Server.Client/ServerClientFactory.cs +++ b/src/Tgstation.Server.Client/ServerClientFactory.cs @@ -13,7 +13,7 @@ namespace Tgstation.Server.Client /// /// The for the /// - static readonly IApiClientFactory apiClientFactory = new ApiClientFactory(); + static readonly IApiClientFactory ApiClientFactory = new ApiClientFactory(); /// /// The for the @@ -40,12 +40,13 @@ namespace Tgstation.Server.Client throw new ArgumentNullException(nameof(password)); Token token; - using (var api = apiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, username, password))) + using (var api = ApiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, username, password))) { if (timeout != default) api.Timeout = timeout; token = await api.Update(Routes.Root, cancellationToken).ConfigureAwait(false); } + return CreateServerClient(host, token, timeout); } @@ -56,7 +57,7 @@ namespace Tgstation.Server.Client throw new ArgumentNullException(nameof(host)); if (token == null) throw new ArgumentNullException(nameof(token)); - var result = new ServerClient(apiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, token.Bearer)), token); + var result = new ServerClient(ApiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, token.Bearer)), token); if (timeout != default) result.Timeout = timeout; return result; diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index c1d23763df..4a74830b44 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -18,18 +18,20 @@ json web api tgstation-server tgstation ss13 byond client Added the ability to change the Token of IServerClient 2018 + ../../build/analyzers.ruleset + latest - latest true bin\Release\netstandard2.0\Tgstation.Server.Client.xml - 0 + 1701;1702 - latest + 1701;1702 + bin\Debug\netstandard2.0\Tgstation.Server.Client.xml @@ -38,10 +40,17 @@ compile; build; native; contentfiles; analyzers + + all + runtime; build; native; contentfiles; analyzers + + + + diff --git a/src/Tgstation.Server.Host.Console/Program.cs b/src/Tgstation.Server.Host.Console/Program.cs index 3e9b566774..9437569d9f 100644 --- a/src/Tgstation.Server.Host.Console/Program.cs +++ b/src/Tgstation.Server.Host.Console/Program.cs @@ -37,6 +37,7 @@ namespace Tgstation.Server.Host.Console loggerFactory.CreateLogger(nameof(Program)).LogCritical("Please specify only 1 of --trace-host-watchdog or --debug-host-watchdog!"); return; } + using (var cts = new CancellationTokenSource()) { void AppDomainHandler(object a, EventArgs b) => cts.Cancel(); diff --git a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj index bb659a9233..c29a5d688d 100644 --- a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj +++ b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj @@ -4,18 +4,20 @@ Exe netcoreapp2.1 Full - 4.0.1.3 + 4.0.1.4 + ../../build/analyzers.ruleset + latest - latest true bin\Release\netcoreapp2.1\Tgstation.Server.Host.Console.xml - latest + 1701;1702;SA1652 + bin\Debug\netcoreapp2.1\Tgstation.Server.Host.Console.xml @@ -24,6 +26,10 @@ compile; build; native; contentfiles; analyzers + + all + runtime; build; native; contentfiles; analyzers + @@ -39,4 +45,7 @@ + + + diff --git a/src/Tgstation.Server.Host.Service/GlobalSuppressions.cs b/src/Tgstation.Server.Host.Service/GlobalSuppressions.cs index 47f7930b2a..dfc5c7f3e3 100644 Binary files a/src/Tgstation.Server.Host.Service/GlobalSuppressions.cs and b/src/Tgstation.Server.Host.Service/GlobalSuppressions.cs differ diff --git a/src/Tgstation.Server.Host.Service/Program.cs b/src/Tgstation.Server.Host.Service/Program.cs index 0c758c4de7..6e519cc9d9 100644 --- a/src/Tgstation.Server.Host.Service/Program.cs +++ b/src/Tgstation.Server.Host.Service/Program.cs @@ -20,6 +20,13 @@ namespace Tgstation.Server.Host.Service /// sealed class Program { +#pragma warning disable SA1401 // Fields should be private + /// + /// The for the + /// + internal static IWatchdogFactory WatchdogFactory = new WatchdogFactory(); +#pragma warning restore SA1401 // Fields should be private + /// /// The --uninstall or -u option /// @@ -50,8 +57,6 @@ namespace Tgstation.Server.Host.Service [Option(ShortName = "d")] public bool Debug { get; set; } - static readonly IWatchdogFactory watchdogFactory = new WatchdogFactory(); - /// /// Check if the running user is a system administrator /// @@ -63,9 +68,15 @@ namespace Tgstation.Server.Host.Service return principal.IsInRole(WindowsBuiltInRole.Administrator); } + /// + /// Entrypoint for the application + /// + static Task Main(string[] args) => CommandLineApplication.ExecuteAsync(args); + /// /// Command line handler, always runs /// + /// A representing the running operation public async Task OnExecuteAsync() { if (Environment.UserInteractive) @@ -81,8 +92,8 @@ namespace Tgstation.Server.Host.Service if (!IsAdministrator()) { - //try to restart as admin - //its windows, first arg is .exe name guaranteed + // try to restart as admin + // its windows, first arg is .exe name guaranteed var exe = Environment.GetCommandLineArgs().First(); var startInfo = new ProcessStartInfo { @@ -100,13 +111,12 @@ namespace Tgstation.Server.Host.Service using (var loggerFactory = new LoggerFactory()) { if (Configure) - await watchdogFactory.CreateWatchdog(loggerFactory).RunAsync(true, Array.Empty(), default).ConfigureAwait(false); + await WatchdogFactory.CreateWatchdog(loggerFactory).RunAsync(true, Array.Empty(), default).ConfigureAwait(false); if (Install) { if (Uninstall) - //oh no, it's retarded... - return; + return; // oh no, it's retarded... using (var processInstaller = new ServiceProcessInstaller()) using (var installer = new ServiceInstaller()) { @@ -132,14 +142,9 @@ namespace Tgstation.Server.Host.Service installer.ServiceName = ServerService.Name; installer.Uninstall(null); } - else if(!Configure) - ServiceBase.Run(new ServerService(watchdogFactory, loggerFactory, Trace ? LogLevel.Trace : Debug ? LogLevel.Debug : LogLevel.Information)); + else if (!Configure) + ServiceBase.Run(new ServerService(WatchdogFactory, loggerFactory, Trace ? LogLevel.Trace : Debug ? LogLevel.Debug : LogLevel.Information)); } } - - /// - /// Entrypoint for the application - /// - static Task Main(string[] args) => CommandLineApplication.ExecuteAsync(args); } } diff --git a/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs b/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs index 88bf8ee48f..2b77d018db 100644 --- a/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs +++ b/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs @@ -7,5 +7,5 @@ using System.Runtime.InteropServices; [assembly: Guid("29927416-3b78-49a7-a560-5ccaa638b6b4")] [assembly: InternalsVisibleTo("Tgstation.Server.Host.Service.Tests")] -[assembly: AssemblyVersion("4.0.1.3")] -[assembly: AssemblyFileVersion("4.0.1.3")] +[assembly: AssemblyVersion("4.0.1.4")] +[assembly: AssemblyFileVersion("4.0.1.4")] diff --git a/src/Tgstation.Server.Host.Service/ServerService.cs b/src/Tgstation.Server.Host.Service/ServerService.cs index 0758c021a3..a0ff1d7ae5 100644 --- a/src/Tgstation.Server.Host.Service/ServerService.cs +++ b/src/Tgstation.Server.Host.Service/ServerService.cs @@ -74,7 +74,7 @@ namespace Tgstation.Server.Host.Service /// A representing the running operation async Task RunWatchdog(string[] args, CancellationToken cancellationToken) { - await watchdog.RunAsync(false, args, cancellationTokenSource.Token).ConfigureAwait(false); + await watchdog.RunAsync(false, args, cancellationToken).ConfigureAwait(false); void StopServiceAsync() { @@ -88,11 +88,12 @@ namespace Tgstation.Server.Host.Service EventLog.WriteEntry(String.Format(CultureInfo.InvariantCulture, "Error stopping service! Exception: {0}", e)); } } + StopServiceAsync(); } /// - [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "cancellationTokenSource")] + [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "cancellationTokenSource", Justification = "IT'S DISPOSED RIGHT THERE YOU FUCCBOI!")] protected override void Dispose(bool disposing) { cancellationTokenSource?.Dispose(); diff --git a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj index c259de621b..6ac929a2b1 100644 --- a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj +++ b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj @@ -26,33 +26,33 @@ false false true + latest + 4 + ../../build/analyzers.ruleset + true + AnyCPU - AnyCPU true full false bin\Debug\ DEBUG;TRACE prompt - 4 - latest - true - AllRules.ruleset + bin\Debug\Tgstation.Server.Host.Service.xml + + SA1101;SA1121;SA1200;SA1202;SA1208;SA1400;SA1413;SA1501;SA1503;SA1519;SA1520;SA1623;SA1629;SA1633;SA1642 - AnyCPU pdbonly true bin\Release\ TRACE prompt - 4 - latest true bin\Release\Tgstation.Server.Host.Service.xml - true - AllRules.ruleset + + SA1101;SA1121;SA1200;SA1202;SA1208;SA1400;SA1413;SA1501;SA1503;SA1519;SA1520;SA1623;SA1629;SA1633;SA1642 Tgstation.Server.Host.Service.Program @@ -104,6 +104,15 @@ 2.1.1 + + all + runtime; build; native; contentfiles; analyzers + + + + + stylecop.json + \ No newline at end of file diff --git a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj index 2d3d915541..6bd413819f 100644 --- a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj +++ b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj @@ -4,18 +4,20 @@ netstandard2.0 Full false - 4.0.1.3 + 4.0.1.4 + ../../build/analyzers.ruleset + latest - latest true bin\Release\netstandard2.0\Tgstation.Server.Host.Watchdog.xml - latest + bin\Debug\netstandard2.0\Tgstation.Server.Host.Watchdog.xml + 1701;1702;SA1652 @@ -24,6 +26,10 @@ compile; build; native; contentfiles; analyzers + + all + runtime; build; native; contentfiles; analyzers + @@ -31,4 +37,8 @@ PreserveNewest + + + + diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index 9dce48ac35..9fce44bfbf 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -15,7 +15,6 @@ namespace Tgstation.Server.Host.Watchdog /// sealed class Watchdog : IWatchdog { - /// /// The for the /// @@ -31,6 +30,7 @@ namespace Tgstation.Server.Host.Watchdog } /// + #pragma warning disable CA1502 // TODO: Decomplexify public async Task RunAsync(bool runConfigure, string[] args, CancellationToken cancellationToken) { logger.LogInformation("Host watchdog starting..."); @@ -67,17 +67,18 @@ namespace Tgstation.Server.Host.Watchdog logger.LogCritical("Unable to locate dotnet executable in PATH! Please ensure the .NET Core runtime is installed and is in your PATH!"); return; } + logger.LogInformation("Detected dotnet executable at {0}", dotnetPath); var rootLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - var assemblyStoragePath = Path.Combine(rootLocation, "lib"); //always always next to watchdog + var assemblyStoragePath = Path.Combine(rootLocation, "lib"); // always always next to watchdog #if DEBUG Directory.CreateDirectory(assemblyStoragePath); #endif var defaultAssemblyPath = Path.GetFullPath(Path.Combine(assemblyStoragePath, "Default")); #if DEBUG - //just copy the shit where it belongs + // just copy the shit where it belongs Directory.Delete(assemblyStoragePath, true); Directory.CreateDirectory(defaultAssemblyPath); @@ -117,7 +118,7 @@ namespace Tgstation.Server.Host.Watchdog using (var process = new Process()) { process.StartInfo.FileName = dotnetPath; - process.StartInfo.WorkingDirectory = rootLocation; //for appsettings + process.StartInfo.WorkingDirectory = rootLocation; // for appsettings var arguments = new List { @@ -138,7 +139,7 @@ namespace Tgstation.Server.Host.Watchdog process.StartInfo.Arguments = String.Join(" ", arguments); - process.StartInfo.UseShellExecute = false; //runs in the same console + process.StartInfo.UseShellExecute = false; // runs in the same console var tcs = new TaskCompletionSource(); process.Exited += (a, b) => @@ -172,7 +173,7 @@ namespace Tgstation.Server.Host.Watchdog { processCts.CancelAfter(TimeSpan.FromSeconds(10)); } - catch (ObjectDisposedException) { } //race conditions + catch (ObjectDisposedException) { } // race conditions })) await tcs.Task.ConfigureAwait(false); } @@ -204,13 +205,12 @@ namespace Tgstation.Server.Host.Watchdog return; case 1: if (!cancellationToken.IsCancellationRequested) - //just a restart - logger.LogInformation("Watchdog will restart host..."); + logger.LogInformation("Watchdog will restart host..."); // just a restart else logger.LogWarning("Host requested restart but watchdog shutdown is in progress!"); break; case 2: - //update path is now an exception document + // update path is now an exception document logger.LogCritical("Host crashed, propagating exception dump..."); var data = File.ReadAllText(updateDirectory); try @@ -221,29 +221,36 @@ namespace Tgstation.Server.Host.Watchdog { logger.LogWarning("Unable to delete exception dump file at {0}! Exception: {1}", updateDirectory, e); } + +#pragma warning disable CA2201 // Do not raise reserved exception types throw new Exception(String.Format(CultureInfo.InvariantCulture, "Host propagated exception: {0}", data)); +#pragma warning restore CA2201 // Do not raise reserved exception types default: if (killedHostProcess) { logger.LogWarning("Watchdog forced to kill host process!"); cancellationToken.ThrowIfCancellationRequested(); } + +#pragma warning disable CA2201 // Do not raise reserved exception types throw new Exception(String.Format(CultureInfo.InvariantCulture, "Host crashed with exit code {0}!", process.ExitCode)); +#pragma warning restore CA2201 // Do not raise reserved exception types } } - //HEY YOU - //BE WARNED THAT IF YOU DEBUGGED THE HOST PROCESS THAT JUST LAUNCHED THE DEBUGGER WILL HOLD A LOCK ON THE DIRECTORY - //THIS MEANS THE FIRST DIRECTORY.MOVE WILL THROW + // HEY YOU + // BE WARNED THAT IF YOU DEBUGGED THE HOST PROCESS THAT JUST LAUNCHED THE DEBUGGER WILL HOLD A LOCK ON THE DIRECTORY + // THIS MEANS THE FIRST DIRECTORY.MOVE WILL THROW if (Directory.Exists(updateDirectory)) { logger.LogInformation("Applying server update..."); if (isWindows) { - //windows dick sucking resource unlocking + // windows dick sucking resource unlocking GC.Collect(Int32.MaxValue, GCCollectionMode.Default, true); await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken).ConfigureAwait(false); } + var tempPath = Path.Combine(assemblyStoragePath, Guid.NewGuid().ToString()); try { @@ -293,5 +300,6 @@ namespace Tgstation.Server.Host.Watchdog logger.LogInformation("Host watchdog exiting..."); } } + #pragma warning restore CA1502 } } diff --git a/src/Tgstation.Server.Host/ClientApp/package-lock.json b/src/Tgstation.Server.Host/ClientApp/package-lock.json new file mode 100644 index 0000000000..67004cb50c --- /dev/null +++ b/src/Tgstation.Server.Host/ClientApp/package-lock.json @@ -0,0 +1,13833 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "http://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, + "@types/node": { + "version": "10.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.0.tgz", + "integrity": "sha512-3TUHC3jsBAB7qVRGxT6lWyYo2v96BMmD2PTcl47H25Lu7UXtFH/2qqmKiVrnel6Ne//0TFYf6uvNX+HW2FRkLQ==", + "dev": true + }, + "@types/prop-types": { + "version": "15.5.6", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.5.6.tgz", + "integrity": "sha512-ZBFR7TROLVzCkswA3Fmqq+IIJt62/T7aY/Dmz+QkU7CaW2QFqAitCE8Ups7IzmGhcN1YWMBT4Qcoc07jU9hOJQ==", + "dev": true + }, + "@types/react": { + "version": "16.4.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.4.18.tgz", + "integrity": "sha512-eFzJKEg6pdeaukVLVZ8Xb79CTl/ysX+ExmOfAAqcFlCCK5TgFDD9kWR0S18sglQ3EmM8U+80enjUqbfnUyqpdA==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "csstype": "^2.2.0" + } + }, + "@types/react-dom": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.0.9.tgz", + "integrity": "sha512-4Z0bW+75zeQgsEg7RaNuS1k9MKhci7oQqZXxrV5KUGIyXZHHAAL3KA4rjhdH8o6foZ5xsRMSqkoM5A3yRVPR5w==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/react": "*" + } + }, + "abab": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", + "integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=", + "dev": true + }, + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "dev": true, + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + } + }, + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "dev": true + }, + "acorn-dynamic-import": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz", + "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=", + "dev": true, + "requires": { + "acorn": "^4.0.3" + } + }, + "acorn-globals": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz", + "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=", + "dev": true, + "requires": { + "acorn": "^4.0.4" + } + }, + "acorn-walk": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.0.tgz", + "integrity": "sha512-ugTb7Lq7u4GfWSqqpwE0bGyoBZNMTok/zDBXxfEG0QM50jNlGhIWjRC1pPN7bvV1anhF+bs+/gNcRw+o55Evbg==", + "dev": true + }, + "address": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/address/-/address-1.0.3.tgz", + "integrity": "sha512-z55ocwKBRLryBs394Sm3ushTtBeg6VAeuku7utSoSnsJKvKcnXFIyC6vh27n3rXyxSgkJBBCAvyOn7gSUcTYjg==", + "dev": true + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "ajv-keywords": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", + "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", + "dev": true + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + } + }, + "alphanum-sort": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", + "dev": true + }, + "ansi-align": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", + "dev": true, + "requires": { + "string-width": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "dev": true + }, + "ansi-html": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "dev": true, + "requires": { + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" + } + }, + "append-transform": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", + "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", + "dev": true, + "requires": { + "default-require-extensions": "^1.0.0" + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", + "dev": true + }, + "array-filter": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", + "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "array-flatten": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.1.tgz", + "integrity": "sha1-Qmu52oQJDBg42BLIFQryCoMx4pY=", + "dev": true + }, + "array-includes": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", + "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" + } + }, + "array-map": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", + "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=", + "dev": true + }, + "array-reduce": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", + "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "dev": true, + "requires": { + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "dev": true, + "requires": { + "lodash": "^4.17.10" + } + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "autoprefixer": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-7.1.6.tgz", + "integrity": "sha512-C9yv/UF3X+eJTi/zvfxuyfxmLibYrntpF3qoJYrMeQwgUJOZrZvpJiMG2FMQ3qnhWtF/be4pYONBBw95ZGe3vA==", + "dev": true, + "requires": { + "browserslist": "^2.5.1", + "caniuse-lite": "^1.0.30000748", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^6.0.13", + "postcss-value-parser": "^3.2.3" + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + } + } + }, + "babel-core": { + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "requires": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "babel-helper-builder-binary-assignment-operator-visitor": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", + "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", + "dev": true, + "requires": { + "babel-helper-explode-assignable-expression": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-builder-react-jsx": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz", + "integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "esutils": "^2.0.2" + } + }, + "babel-helper-call-delegate": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", + "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", + "dev": true, + "requires": { + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-define-map": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", + "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", + "dev": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "babel-helper-explode-assignable-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", + "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", + "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", + "dev": true, + "requires": { + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-get-function-arity": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", + "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-hoist-variables": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", + "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-optimise-call-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", + "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-regex": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", + "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "babel-helper-remap-async-to-generator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", + "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", + "dev": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-replace-supers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", + "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", + "dev": true, + "requires": { + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helpers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-jest": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-20.0.3.tgz", + "integrity": "sha1-5KA7E9wQOJ4UD8ZF0J/8TO0wFnE=", + "dev": true, + "requires": { + "babel-core": "^6.0.0", + "babel-plugin-istanbul": "^4.0.0", + "babel-preset-jest": "^20.0.3" + } + }, + "babel-loader": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-7.1.2.tgz", + "integrity": "sha512-jRwlFbINAeyDStqK6Dd5YuY0k5YuzQUvlz2ZamuXrXmxav3pNqe9vfJ402+2G+OmlJSXxCOpB6Uz0INM7RQe2A==", + "dev": true, + "requires": { + "find-cache-dir": "^1.0.0", + "loader-utils": "^1.0.2", + "mkdirp": "^0.5.1" + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-check-es2015-constants": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", + "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-dynamic-import-node": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-1.1.0.tgz", + "integrity": "sha512-tTfZbM9Ecwj3GK50mnPrUpinTwA4xXmDiQGCk/aBYbvl1+X8YqldK86wZ1owVJ4u3mrKbRlXMma80J18qwiaTQ==", + "dev": true, + "requires": { + "babel-plugin-syntax-dynamic-import": "^6.18.0", + "babel-template": "^6.26.0", + "babel-types": "^6.26.0" + } + }, + "babel-plugin-istanbul": { + "version": "4.1.6", + "resolved": "http://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", + "integrity": "sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==", + "dev": true, + "requires": { + "babel-plugin-syntax-object-rest-spread": "^6.13.0", + "find-up": "^2.1.0", + "istanbul-lib-instrument": "^1.10.1", + "test-exclude": "^4.2.1" + } + }, + "babel-plugin-jest-hoist": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-20.0.3.tgz", + "integrity": "sha1-r+3IU70/jcNUjqZx++adA8wsF2c=", + "dev": true + }, + "babel-plugin-syntax-async-functions": { + "version": "6.13.0", + "resolved": "http://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", + "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", + "dev": true + }, + "babel-plugin-syntax-class-properties": { + "version": "6.13.0", + "resolved": "http://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", + "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=", + "dev": true + }, + "babel-plugin-syntax-dynamic-import": { + "version": "6.18.0", + "resolved": "http://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz", + "integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=", + "dev": true + }, + "babel-plugin-syntax-exponentiation-operator": { + "version": "6.13.0", + "resolved": "http://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", + "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", + "dev": true + }, + "babel-plugin-syntax-flow": { + "version": "6.18.0", + "resolved": "http://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", + "integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=", + "dev": true + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "http://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", + "dev": true + }, + "babel-plugin-syntax-object-rest-spread": { + "version": "6.13.0", + "resolved": "http://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", + "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", + "dev": true + }, + "babel-plugin-syntax-trailing-function-commas": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", + "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", + "dev": true + }, + "babel-plugin-transform-async-to-generator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", + "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", + "dev": true, + "requires": { + "babel-helper-remap-async-to-generator": "^6.24.1", + "babel-plugin-syntax-async-functions": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-class-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz", + "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", + "dev": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-plugin-syntax-class-properties": "^6.8.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-arrow-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", + "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-block-scoped-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", + "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-block-scoping": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", + "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "babel-plugin-transform-es2015-classes": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", + "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", + "dev": true, + "requires": { + "babel-helper-define-map": "^6.24.1", + "babel-helper-function-name": "^6.24.1", + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-helper-replace-supers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-computed-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", + "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-destructuring": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", + "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-duplicate-keys": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", + "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-for-of": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", + "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", + "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", + "dev": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", + "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-modules-amd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", + "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-modules-commonjs": { + "version": "6.26.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", + "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", + "dev": true, + "requires": { + "babel-plugin-transform-strict-mode": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-types": "^6.26.0" + } + }, + "babel-plugin-transform-es2015-modules-systemjs": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", + "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", + "dev": true, + "requires": { + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-modules-umd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", + "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-object-super": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", + "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", + "dev": true, + "requires": { + "babel-helper-replace-supers": "^6.24.1", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-parameters": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", + "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", + "dev": true, + "requires": { + "babel-helper-call-delegate": "^6.24.1", + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-shorthand-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", + "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-spread": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", + "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-sticky-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", + "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", + "dev": true, + "requires": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-template-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", + "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-typeof-symbol": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", + "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-unicode-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", + "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", + "dev": true, + "requires": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "regexpu-core": "^2.0.0" + } + }, + "babel-plugin-transform-exponentiation-operator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", + "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", + "dev": true, + "requires": { + "babel-helper-builder-binary-assignment-operator-visitor": "^6.24.1", + "babel-plugin-syntax-exponentiation-operator": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-flow-strip-types": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz", + "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=", + "dev": true, + "requires": { + "babel-plugin-syntax-flow": "^6.18.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-object-rest-spread": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz", + "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=", + "dev": true, + "requires": { + "babel-plugin-syntax-object-rest-spread": "^6.8.0", + "babel-runtime": "^6.26.0" + } + }, + "babel-plugin-transform-react-constant-elements": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-constant-elements/-/babel-plugin-transform-react-constant-elements-6.23.0.tgz", + "integrity": "sha1-LxGb9NLN1F65uqrldAU8YE9hR90=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-react-display-name": { + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz", + "integrity": "sha1-Z+K/Hx6ck6sI25Z5LgU5K/LMKNE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-react-jsx": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz", + "integrity": "sha1-hAoCjn30YN/DotKfDA2R9jduZqM=", + "dev": true, + "requires": { + "babel-helper-builder-react-jsx": "^6.24.1", + "babel-plugin-syntax-jsx": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-react-jsx-self": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz", + "integrity": "sha1-322AqdomEqEh5t3XVYvL7PBuY24=", + "dev": true, + "requires": { + "babel-plugin-syntax-jsx": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-react-jsx-source": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz", + "integrity": "sha1-ZqwSFT9c0tF7PBkmj0vwGX9E7NY=", + "dev": true, + "requires": { + "babel-plugin-syntax-jsx": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-regenerator": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", + "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", + "dev": true, + "requires": { + "regenerator-transform": "^0.10.0" + } + }, + "babel-plugin-transform-runtime": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz", + "integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-strict-mode": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", + "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-preset-env": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.6.1.tgz", + "integrity": "sha512-W6VIyA6Ch9ePMI7VptNn2wBM6dbG0eSz25HEiL40nQXCsXGTGZSTZu1Iap+cj3Q0S5a7T9+529l/5Bkvd+afNA==", + "dev": true, + "requires": { + "babel-plugin-check-es2015-constants": "^6.22.0", + "babel-plugin-syntax-trailing-function-commas": "^6.22.0", + "babel-plugin-transform-async-to-generator": "^6.22.0", + "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoping": "^6.23.0", + "babel-plugin-transform-es2015-classes": "^6.23.0", + "babel-plugin-transform-es2015-computed-properties": "^6.22.0", + "babel-plugin-transform-es2015-destructuring": "^6.23.0", + "babel-plugin-transform-es2015-duplicate-keys": "^6.22.0", + "babel-plugin-transform-es2015-for-of": "^6.23.0", + "babel-plugin-transform-es2015-function-name": "^6.22.0", + "babel-plugin-transform-es2015-literals": "^6.22.0", + "babel-plugin-transform-es2015-modules-amd": "^6.22.0", + "babel-plugin-transform-es2015-modules-commonjs": "^6.23.0", + "babel-plugin-transform-es2015-modules-systemjs": "^6.23.0", + "babel-plugin-transform-es2015-modules-umd": "^6.23.0", + "babel-plugin-transform-es2015-object-super": "^6.22.0", + "babel-plugin-transform-es2015-parameters": "^6.23.0", + "babel-plugin-transform-es2015-shorthand-properties": "^6.22.0", + "babel-plugin-transform-es2015-spread": "^6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "^6.22.0", + "babel-plugin-transform-es2015-template-literals": "^6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "^6.23.0", + "babel-plugin-transform-es2015-unicode-regex": "^6.22.0", + "babel-plugin-transform-exponentiation-operator": "^6.22.0", + "babel-plugin-transform-regenerator": "^6.22.0", + "browserslist": "^2.1.2", + "invariant": "^2.2.2", + "semver": "^5.3.0" + } + }, + "babel-preset-flow": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz", + "integrity": "sha1-5xIYiHCFrpoktb5Baa/7WZgWxJ0=", + "dev": true, + "requires": { + "babel-plugin-transform-flow-strip-types": "^6.22.0" + } + }, + "babel-preset-jest": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-20.0.3.tgz", + "integrity": "sha1-y6yq3stdaJyh4d4TYOv8ZoYsF4o=", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^20.0.3" + } + }, + "babel-preset-react": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-react/-/babel-preset-react-6.24.1.tgz", + "integrity": "sha1-umnfrqRfw+xjm2pOzqbhdwLJE4A=", + "dev": true, + "requires": { + "babel-plugin-syntax-jsx": "^6.3.13", + "babel-plugin-transform-react-display-name": "^6.23.0", + "babel-plugin-transform-react-jsx": "^6.24.1", + "babel-plugin-transform-react-jsx-self": "^6.22.0", + "babel-plugin-transform-react-jsx-source": "^6.22.0", + "babel-preset-flow": "^6.23.0" + } + }, + "babel-preset-react-app": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-3.1.2.tgz", + "integrity": "sha512-/sh5Qd5T08PYa6t4kuCdKh9tXp6/m/Jwyx7PJTqugsYMfsDUJMlBXOs5EwFODHprzjWrmQ0SydnMZu9FY4MZYg==", + "dev": true, + "requires": { + "babel-plugin-dynamic-import-node": "1.1.0", + "babel-plugin-syntax-dynamic-import": "6.18.0", + "babel-plugin-transform-class-properties": "6.24.1", + "babel-plugin-transform-es2015-destructuring": "6.23.0", + "babel-plugin-transform-object-rest-spread": "6.26.0", + "babel-plugin-transform-react-constant-elements": "6.23.0", + "babel-plugin-transform-react-jsx": "6.24.1", + "babel-plugin-transform-react-jsx-self": "6.22.0", + "babel-plugin-transform-react-jsx-source": "6.22.0", + "babel-plugin-transform-regenerator": "6.26.0", + "babel-plugin-transform-runtime": "6.23.0", + "babel-preset-env": "1.6.1", + "babel-preset-react": "6.24.1" + } + }, + "babel-register": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", + "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "dev": true, + "requires": { + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "home-or-tmp": "^2.0.0", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "source-map-support": "^0.4.15" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", + "dev": true + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "dev": true + }, + "binary-extensions": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz", + "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==", + "dev": true + }, + "bluebird": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz", + "integrity": "sha512-dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg==", + "dev": true + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + }, + "body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, + "bonjour": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", + "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "dev": true, + "requires": { + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "boxen": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", + "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", + "dev": true, + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "requires": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browser-process-hrtime": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", + "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", + "dev": true + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "dev": true, + "requires": { + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + } + } + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true, + "requires": { + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "2.11.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", + "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000792", + "electron-to-chromium": "^1.3.30" + } + }, + "bser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz", + "integrity": "sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk=", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer": { + "version": "4.9.1", + "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "buffer-indexof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", + "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + }, + "cacache": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", + "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", + "dev": true, + "requires": { + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.1", + "mississippi": "^2.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^5.2.4", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" + }, + "dependencies": { + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + } + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true + }, + "camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "dev": true, + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + } + } + }, + "caniuse-api": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-1.6.1.tgz", + "integrity": "sha1-tTTnxzTE+B7F++isoq0kNUuWLGw=", + "dev": true, + "requires": { + "browserslist": "^1.3.6", + "caniuse-db": "^1.0.30000529", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + }, + "dependencies": { + "browserslist": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", + "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", + "dev": true, + "requires": { + "caniuse-db": "^1.0.30000639", + "electron-to-chromium": "^1.2.7" + } + } + } + }, + "caniuse-db": { + "version": "1.0.30000893", + "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000893.tgz", + "integrity": "sha512-JfGWsX2JtyXnjGAAjXMHiv4OEGFZvArB5pxdd5oa5xCuEYYN1rhBMwogMIKnKoUBi6lKRyO4JQAPJwqchv20Yg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30000893", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000893.tgz", + "integrity": "sha512-kOddHcTEef+NgN/fs0zmX2brHTNATVOWMEIhlZHCuwQRtXobjSw9pAECc44Op4bTBcavRjkLaPrGomknH7+Jvg==", + "dev": true + }, + "capture-stack-trace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", + "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==", + "dev": true + }, + "case-sensitive-paths-webpack-plugin": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.1.1.tgz", + "integrity": "sha1-PSnO2MHxJL9vU4Rvs/WJRzH9yQk=", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, + "chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "dev": true, + "requires": { + "anymatch": "^1.3.0", + "async-each": "^1.0.0", + "fsevents": "^1.0.0", + "glob-parent": "^2.0.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^2.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0" + } + }, + "chownr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "dev": true + }, + "ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", + "dev": true + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "clap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", + "integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==", + "dev": true, + "requires": { + "chalk": "^1.1.3" + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "clean-css": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + } + }, + "cli-boxes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "coa": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.4.tgz", + "integrity": "sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0=", + "dev": true, + "requires": { + "q": "^1.1.2" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color": { + "version": "0.11.4", + "resolved": "http://registry.npmjs.org/color/-/color-0.11.4.tgz", + "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=", + "dev": true, + "requires": { + "clone": "^1.0.2", + "color-convert": "^1.3.0", + "color-string": "^0.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "color-string": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", + "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", + "dev": true, + "requires": { + "color-name": "^1.0.0" + } + }, + "colormin": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colormin/-/colormin-1.1.2.tgz", + "integrity": "sha1-6i90IKcrlogaOKrlnsEkpvcpgTM=", + "dev": true, + "requires": { + "color": "^0.11.0", + "css-color-names": "0.0.4", + "has": "^1.0.1" + } + }, + "colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "dev": true + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "compressible": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.15.tgz", + "integrity": "sha512-4aE67DL33dSW9gw4CI2H/yTxqHLNcxp0yS6jB+4h+wr3e43+1z7vm0HU9qXOH8j+qjKuL8+UtkOxYQSMq60Ylw==", + "dev": true, + "requires": { + "mime-db": ">= 1.36.0 < 2" + } + }, + "compression": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz", + "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.14", + "debug": "2.6.9", + "on-headers": "~1.0.1", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "configstore": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", + "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", + "dev": true, + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "connect-history-api-fallback": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", + "integrity": "sha1-sGhzk0vF40T+9hGhlqb6rgruAVo=", + "dev": true + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "^0.1.4" + } + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "dev": true + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "content-type-parser": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/content-type-parser/-/content-type-parser-1.0.2.tgz", + "integrity": "sha512-lM4l4CnMEwOLHAHr/P6MEZwZFPJFtAAKgL6pogbXmVZggIqXhdB6RbBtPOTsw2FcXwYhehRGERJmRrjOiIB8pQ==", + "dev": true + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-js": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cosmiconfig": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-2.2.2.tgz", + "integrity": "sha512-GiNXLwAFPYHy25XmTPpafYvn3CLAkJ8FLsscq78MQd1Kh0OU6Yzhn4eV2MVF4G9WEQZoWEGltatdR+ntGPMl5A==", + "dev": true, + "requires": { + "is-directory": "^0.3.1", + "js-yaml": "^3.4.3", + "minimist": "^1.2.0", + "object-assign": "^4.1.0", + "os-homedir": "^1.0.1", + "parse-json": "^2.2.0", + "require-from-string": "^1.1.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "cp-cli": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cp-cli/-/cp-cli-1.1.2.tgz", + "integrity": "sha1-sk4f24sHonzjh5mVyMDG1nyqi4Y=", + "dev": true, + "requires": { + "fs-extra": "5.0.0", + "yargs": "11.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "fs-extra": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", + "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "dev": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "yargs": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.0.0.tgz", + "integrity": "sha512-Rjp+lMYQOWtgqojx1dEWorjCofi1YN7AoFvYV7b1gx/7dAAeuI4kN5SZiEvr0ZmsZTOpDRcCqrpI10L31tFkBw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^9.0.2" + } + }, + "yargs-parser": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", + "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "cpx": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/cpx/-/cpx-1.5.0.tgz", + "integrity": "sha1-GFvgGFEdhycN7czCkxceN2VauI8=", + "dev": true, + "requires": { + "babel-runtime": "^6.9.2", + "chokidar": "^1.6.0", + "duplexer": "^0.1.1", + "glob": "^7.0.5", + "glob2base": "^0.0.12", + "minimatch": "^3.0.2", + "mkdirp": "^0.5.1", + "resolve": "^1.1.7", + "safe-buffer": "^5.0.1", + "shell-quote": "^1.6.1", + "subarg": "^1.0.0" + } + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + } + }, + "create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "dev": true, + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", + "dev": true + }, + "css-color-names": { + "version": "0.0.4", + "resolved": "http://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", + "dev": true + }, + "css-loader": { + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-0.28.7.tgz", + "integrity": "sha512-GxMpax8a/VgcfRrVy0gXD6yLd5ePYbXX/5zGgTVYp4wXtJklS8Z2VaUArJgc//f6/Dzil7BaJObdSv8eKKCPgg==", + "dev": true, + "requires": { + "babel-code-frame": "^6.11.0", + "css-selector-tokenizer": "^0.7.0", + "cssnano": ">=2.6.1 <4", + "icss-utils": "^2.1.0", + "loader-utils": "^1.0.2", + "lodash.camelcase": "^4.3.0", + "object-assign": "^4.0.1", + "postcss": "^5.0.6", + "postcss-modules-extract-imports": "^1.0.0", + "postcss-modules-local-by-default": "^1.0.1", + "postcss-modules-scope": "^1.0.0", + "postcss-modules-values": "^1.1.0", + "postcss-value-parser": "^3.3.0", + "source-list-map": "^2.0.0" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "css-select": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-selector-tokenizer": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz", + "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=", + "dev": true, + "requires": { + "cssesc": "^0.1.0", + "fastparse": "^1.1.1", + "regexpu-core": "^1.0.0" + }, + "dependencies": { + "regexpu-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", + "dev": true, + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + } + } + }, + "css-what": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.2.tgz", + "integrity": "sha512-wan8dMWQ0GUeF7DGEPVjhHemVW/vy6xUYmFzRY8RYqgA0JtXC9rJmbScBjqSu6dg9q0lwPQy6ZAmJVr3PPTvqQ==", + "dev": true + }, + "cssesc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", + "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", + "dev": true + }, + "cssnano": { + "version": "3.10.0", + "resolved": "http://registry.npmjs.org/cssnano/-/cssnano-3.10.0.tgz", + "integrity": "sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg=", + "dev": true, + "requires": { + "autoprefixer": "^6.3.1", + "decamelize": "^1.1.2", + "defined": "^1.0.0", + "has": "^1.0.1", + "object-assign": "^4.0.1", + "postcss": "^5.0.14", + "postcss-calc": "^5.2.0", + "postcss-colormin": "^2.1.8", + "postcss-convert-values": "^2.3.4", + "postcss-discard-comments": "^2.0.4", + "postcss-discard-duplicates": "^2.0.1", + "postcss-discard-empty": "^2.0.1", + "postcss-discard-overridden": "^0.1.1", + "postcss-discard-unused": "^2.2.1", + "postcss-filter-plugins": "^2.0.0", + "postcss-merge-idents": "^2.1.5", + "postcss-merge-longhand": "^2.0.1", + "postcss-merge-rules": "^2.0.3", + "postcss-minify-font-values": "^1.0.2", + "postcss-minify-gradients": "^1.0.1", + "postcss-minify-params": "^1.0.4", + "postcss-minify-selectors": "^2.0.4", + "postcss-normalize-charset": "^1.1.0", + "postcss-normalize-url": "^3.0.7", + "postcss-ordered-values": "^2.1.0", + "postcss-reduce-idents": "^2.2.2", + "postcss-reduce-initial": "^1.0.0", + "postcss-reduce-transforms": "^1.0.3", + "postcss-svgo": "^2.1.1", + "postcss-unique-selectors": "^2.0.2", + "postcss-value-parser": "^3.2.3", + "postcss-zindex": "^2.0.1" + }, + "dependencies": { + "autoprefixer": { + "version": "6.7.7", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz", + "integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=", + "dev": true, + "requires": { + "browserslist": "^1.7.6", + "caniuse-db": "^1.0.30000634", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^5.2.16", + "postcss-value-parser": "^3.2.3" + } + }, + "browserslist": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", + "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", + "dev": true, + "requires": { + "caniuse-db": "^1.0.30000639", + "electron-to-chromium": "^1.2.7" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "csso": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/csso/-/csso-2.3.2.tgz", + "integrity": "sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U=", + "dev": true, + "requires": { + "clap": "^1.0.9", + "source-map": "^0.5.3" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "cssom": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.4.tgz", + "integrity": "sha512-+7prCSORpXNeR4/fUP3rL+TzqtiFfhMvTd7uEqMdgPvLPt4+uzFUeufx5RHjGTACCargg/DiEt/moMQmvnfkog==", + "dev": true + }, + "cssstyle": { + "version": "0.2.37", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz", + "integrity": "sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ=", + "dev": true, + "requires": { + "cssom": "0.3.x" + } + }, + "csstype": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.5.7.tgz", + "integrity": "sha512-Nt5VDyOTIIV4/nRFswoCKps1R5CD1hkiyjBE9/thNaNZILLEviVw9yWQw15+O+CpNjQKB/uvdcxFFOrSflY3Yw==", + "dev": true + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "cyclist": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", + "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", + "dev": true + }, + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "dev": true, + "requires": { + "es5-ext": "^0.10.9" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.0.1.tgz", + "integrity": "sha512-0HdcMZzK6ubMUnsMmQmG0AcLQPvbvb47R0+7CCZQCYgcd8OUWG91CG7sM6GoXgjz+WLl4ArFzHtBMy/QqSF4eg==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "whatwg-mimetype": "^2.1.0", + "whatwg-url": "^7.0.0" + }, + "dependencies": { + "abab": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", + "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "whatwg-url": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", + "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } + } + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "dev": true, + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + } + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "deepmerge": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", + "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==", + "dev": true + }, + "default-require-extensions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", + "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", + "dev": true, + "requires": { + "strip-bom": "^2.0.0" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "des.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "detect-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", + "dev": true + }, + "detect-port-alt": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", + "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", + "dev": true, + "requires": { + "address": "^1.0.1", + "debug": "^2.6.0" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", + "dev": true + }, + "dns-packet": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", + "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "dev": true, + "requires": { + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "dns-txt": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "dev": true, + "requires": { + "buffer-indexof": "^1.0.0" + } + }, + "dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "requires": { + "utila": "~0.4" + } + }, + "dom-serializer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "dev": true, + "requires": { + "domelementtype": "~1.1.1", + "entities": "~1.1.1" + }, + "dependencies": { + "domelementtype": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", + "dev": true + } + } + }, + "dom-urls": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/dom-urls/-/dom-urls-1.1.0.tgz", + "integrity": "sha1-AB3fgWKM0ecGElxxdvU8zsVdkY4=", + "dev": true, + "requires": { + "urijs": "^1.16.1" + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true + }, + "domelementtype": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", + "dev": true + }, + "domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, + "requires": { + "webidl-conversions": "^4.0.2" + } + }, + "domhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.1.0.tgz", + "integrity": "sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ=", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "dotenv": { + "version": "4.0.0", + "resolved": "http://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz", + "integrity": "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0=", + "dev": true + }, + "dotenv-expand": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-4.2.0.tgz", + "integrity": "sha1-3vHxyl1gWdJKdm5YeULCEQbOEnU=", + "dev": true + }, + "duplexer": { + "version": "0.1.1", + "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "dev": true + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "duplexify": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz", + "integrity": "sha512-vM58DwdnKmty+FSPzT14K9JXb90H+j5emaR4KYbr2KTIz00WHGbWOe5ghQTx233ZCLZtrGDALzKwcjEtSt35mA==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.80", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.80.tgz", + "integrity": "sha512-WClidEWEUNx7OfwXehB0qaxCuetjbKjev2SmXWgybWPLKAThBiMTF/2Pd8GSUDtoGOavxVzdkKwfFAPRSWlkLw==", + "dev": true + }, + "elliptic": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", + "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", + "dev": true, + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "enhanced-resolve": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz", + "integrity": "sha1-BCHjOf1xQZs9oT0Smzl5BAIwR24=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "object-assign": "^4.0.1", + "tapable": "^0.2.7" + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", + "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", + "dev": true, + "requires": { + "es-to-primitive": "^1.1.1", + "function-bind": "^1.1.1", + "has": "^1.0.1", + "is-callable": "^1.1.3", + "is-regex": "^1.0.4" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es5-ext": { + "version": "0.10.46", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz", + "integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==", + "dev": true, + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "1" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-set": "~0.1.5", + "es6-symbol": "~3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-promise": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz", + "integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==", + "dev": true + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-symbol": "3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "es6-weak-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", + "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.14", + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escodegen": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.0.tgz", + "integrity": "sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==", + "dev": true, + "requires": { + "esprima": "^3.1.3", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true + } + } + }, + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "requires": { + "es6-map": "^0.1.3", + "es6-weak-map": "^2.0.1", + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "eventemitter3": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", + "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==", + "dev": true + }, + "events": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "dev": true + }, + "eventsource": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-0.1.6.tgz", + "integrity": "sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI=", + "dev": true, + "requires": { + "original": ">=0.0.5" + } + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "exec-sh": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.2.tgz", + "integrity": "sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw==", + "dev": true, + "requires": { + "merge": "^1.2.0" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "requires": { + "is-posix-bracket": "^0.1.0" + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true, + "requires": { + "fill-range": "^2.1.0" + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "expect": { + "version": "22.4.3", + "resolved": "http://registry.npmjs.org/expect/-/expect-22.4.3.tgz", + "integrity": "sha512-XcNXEPehqn8b/jm8FYotdX0YrXn36qp4HWlrVT4ktwQas1l1LPxiVWncYnnL2eyMtKAmVIaG0XAp0QlrqJaxaA==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "jest-diff": "^22.4.3", + "jest-get-type": "^22.4.3", + "jest-matcher-utils": "^22.4.3", + "jest-message-util": "^22.4.3", + "jest-regex-util": "^22.4.3" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "jest-diff": { + "version": "22.4.3", + "resolved": "http://registry.npmjs.org/jest-diff/-/jest-diff-22.4.3.tgz", + "integrity": "sha512-/QqGvCDP5oZOF6PebDuLwrB2BMD8ffJv6TAGAdEVuDx1+uEgrHpSFrfrOiMRx2eJ1hgNjlQrOQEHetVwij90KA==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "diff": "^3.2.0", + "jest-get-type": "^22.4.3", + "pretty-format": "^22.4.3" + } + }, + "jest-matcher-utils": { + "version": "22.4.3", + "resolved": "http://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-22.4.3.tgz", + "integrity": "sha512-lsEHVaTnKzdAPR5t4B6OcxXo9Vy4K+kRRbG5gtddY8lBEC+Mlpvm1CJcsMESRjzUhzkz568exMV1hTB76nAKbA==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "jest-get-type": "^22.4.3", + "pretty-format": "^22.4.3" + } + }, + "jest-message-util": { + "version": "22.4.3", + "resolved": "http://registry.npmjs.org/jest-message-util/-/jest-message-util-22.4.3.tgz", + "integrity": "sha512-iAMeKxhB3Se5xkSjU0NndLLCHtP4n+GtCqV0bISKA5dmOXQfEbdEmYiu2qpnWBDCQdEafNDDU6Q+l6oBMd/+BA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0-beta.35", + "chalk": "^2.0.1", + "micromatch": "^2.3.11", + "slash": "^1.0.0", + "stack-utils": "^1.0.1" + } + }, + "jest-regex-util": { + "version": "22.4.3", + "resolved": "http://registry.npmjs.org/jest-regex-util/-/jest-regex-util-22.4.3.tgz", + "integrity": "sha512-LFg1gWr3QinIjb8j833bq7jtQopiwdAs67OGfkPrvy7uNUbVMfTXXcOKXJaeY5GgjobELkKvKENqq1xrUectWg==", + "dev": true + }, + "pretty-format": { + "version": "22.4.3", + "resolved": "http://registry.npmjs.org/pretty-format/-/pretty-format-22.4.3.tgz", + "integrity": "sha512-S4oT9/sT6MN7/3COoOy+ZJeA92VmOnveLHgrwBE3Z1W5N9S2A1QGNYiE1z75DAENbJrXXUb+OWXhpJcg05QKQQ==", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0", + "ansi-styles": "^3.2.0" + } + } + } + }, + "express": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", + "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.3", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.4", + "qs": "6.5.2", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.2", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "external-editor": { + "version": "2.2.0", + "resolved": "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "dev": true, + "requires": { + "chardet": "^0.4.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "extract-text-webpack-plugin": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extract-text-webpack-plugin/-/extract-text-webpack-plugin-3.0.2.tgz", + "integrity": "sha512-bt/LZ4m5Rqt/Crl2HiKuAl/oqg0psx1tsTLkvWbJen1CtD+fftkZhMaQ9HOtY2gWsl2Wq+sABmMVi9z3DhKWQQ==", + "dev": true, + "requires": { + "async": "^2.4.1", + "loader-utils": "^1.1.0", + "schema-utils": "^0.3.0", + "webpack-sources": "^1.0.1" + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fastparse": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz", + "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=", + "dev": true + }, + "faye-websocket": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz", + "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "fb-watchman": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", + "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", + "dev": true, + "requires": { + "bser": "^2.0.0" + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-loader": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.5.tgz", + "integrity": "sha512-RzGHDatcVNpGISTvCpfUfOGpYuSR7HSsSg87ki+wF6rw1Hm0RALPTiAdsxAq1UwLf0RRhbe22/eHK6nhXspiOQ==", + "dev": true, + "requires": { + "loader-utils": "^1.0.2", + "schema-utils": "^0.3.0" + } + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true + }, + "fileset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", + "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", + "dev": true, + "requires": { + "glob": "^7.0.3", + "minimatch": "^3.0.3" + } + }, + "filesize": { + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.5.11.tgz", + "integrity": "sha512-ZH7loueKBoDb7yG9esn1U+fgq7BzlzW6NRi5/rMdxIZ05dj7GFD/Xc5rq2CDt5Yq86CyfSYVyx4242QQNZbx1g==", + "dev": true + }, + "fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "dev": true, + "requires": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + } + }, + "finalhandler": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + } + }, + "find-cache-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", + "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^2.0.0" + } + }, + "find-index": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz", + "integrity": "sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ=", + "dev": true + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flatten": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", + "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=", + "dev": true + }, + "flush-write-stream": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", + "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" + } + }, + "follow-redirects": { + "version": "1.5.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.9.tgz", + "integrity": "sha512-Bh65EZI/RU8nx0wbYF9shkFZlqLP+6WT/5FnA3cE/djNSuKNHJEinGGZgu/cQEkeeb2GdFOgenAmn8qaqYke2w==", + "dev": true, + "requires": { + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "fork-ts-checker-webpack-plugin": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-0.2.10.tgz", + "integrity": "sha1-0KQIDnfp9dbjtDzc59JmWPnSUMY=", + "dev": true, + "requires": { + "babel-code-frame": "^6.22.0", + "chalk": "^1.1.3", + "chokidar": "^1.7.0", + "lodash.endswith": "^4.2.1", + "lodash.isfunction": "^3.0.8", + "lodash.isstring": "^4.0.1", + "lodash.startswith": "^4.2.1", + "minimatch": "^3.0.4" + } + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-extra": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", + "integrity": "sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^3.0.0", + "universalify": "^0.1.0" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "^2.1.0" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true, + "dev": true + } + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true, + "requires": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "^2.0.0" + } + }, + "glob2base": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz", + "integrity": "sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY=", + "dev": true, + "requires": { + "find-index": "^0.1.1" + } + }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "dev": true, + "requires": { + "ini": "^1.3.4" + } + }, + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "got": { + "version": "6.7.1", + "resolved": "http://registry.npmjs.org/got/-/got-6.7.1.tgz", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "dev": true, + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "dev": true + }, + "gzip-size": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-3.0.0.tgz", + "integrity": "sha1-VGGI6b3DN/Zzdy+BZgRks4nc5SA=", + "dev": true, + "requires": { + "duplexer": "^0.1.1" + } + }, + "handle-thing": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz", + "integrity": "sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ=", + "dev": true + }, + "handlebars": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.12.tgz", + "integrity": "sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==", + "dev": true, + "requires": { + "async": "^2.5.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", + "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", + "dev": true, + "requires": { + "ajv": "^5.3.0", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "hash.js": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.5.tgz", + "integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "home-or-tmp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.1" + } + }, + "homedir-polyfill": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", + "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "dev": true + }, + "hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "html-comment-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", + "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==", + "dev": true + }, + "html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.1" + } + }, + "html-entities": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", + "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", + "dev": true + }, + "html-minifier": { + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.20.tgz", + "integrity": "sha512-ZmgNLaTp54+HFKkONyLFEfs5dd/ZOtlquKaTnqIWFmx3Av5zG6ZPcV2d0o9XM2fXOTxxIf6eDcwzFFotke/5zA==", + "dev": true, + "requires": { + "camel-case": "3.0.x", + "clean-css": "4.2.x", + "commander": "2.17.x", + "he": "1.1.x", + "param-case": "2.1.x", + "relateurl": "0.2.x", + "uglify-js": "3.4.x" + } + }, + "html-webpack-plugin": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-2.29.0.tgz", + "integrity": "sha1-6Yf0IYU9O2k4yMTIFxhC5f0XryM=", + "dev": true, + "requires": { + "bluebird": "^3.4.7", + "html-minifier": "^3.2.3", + "loader-utils": "^0.2.16", + "lodash": "^4.17.3", + "pretty-error": "^2.0.2", + "toposort": "^1.0.0" + }, + "dependencies": { + "loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true, + "requires": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0", + "object-assign": "^4.0.1" + } + } + } + }, + "htmlparser2": { + "version": "3.3.0", + "resolved": "http://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", + "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=", + "dev": true, + "requires": { + "domelementtype": "1", + "domhandler": "2.1", + "domutils": "1.1", + "readable-stream": "1.0" + }, + "dependencies": { + "domutils": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.1.6.tgz", + "integrity": "sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU=", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", + "dev": true + }, + "http-errors": { + "version": "1.6.3", + "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "http-parser-js": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.13.tgz", + "integrity": "sha1-O9bW/ebjFyyTNMOzO2wZPYD+ETc=", + "dev": true + }, + "http-proxy": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", + "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "dev": true, + "requires": { + "eventemitter3": "^3.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-middleware": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz", + "integrity": "sha1-ZC6ISIUdZvCdTxJJEoRtuutBuDM=", + "dev": true, + "requires": { + "http-proxy": "^1.16.2", + "is-glob": "^3.1.0", + "lodash": "^4.17.2", + "micromatch": "^2.3.11" + }, + "dependencies": { + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", + "dev": true + }, + "icss-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-2.1.0.tgz", + "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=", + "dev": true, + "requires": { + "postcss": "^6.0.1" + } + }, + "ieee754": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", + "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==", + "dev": true + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true + }, + "import-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", + "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", + "dev": true, + "requires": { + "pkg-dir": "^2.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", + "dev": true + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.0.4", + "figures": "^2.0.0", + "lodash": "^4.3.0", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rx-lite": "^4.0.8", + "rx-lite-aggregates": "^4.0.8", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-escapes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "internal-ip": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-1.2.0.tgz", + "integrity": "sha1-rp+/k7mEh4eF1QqN4bNWlWBYz1w=", + "dev": true, + "requires": { + "meow": "^3.3.0" + } + }, + "interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, + "ipaddr.js": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", + "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=", + "dev": true + }, + "is-absolute-url": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", + "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "requires": { + "builtin-modules": "^1.0.0" + } + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-ci": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "dev": true, + "requires": { + "ci-info": "^1.5.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true, + "requires": { + "is-primitive": "^2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-generator-fn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-1.0.0.tgz", + "integrity": "sha1-lp1J4bszKfa7fwkIm+JleLLd1Go=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "is-installed-globally": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", + "dev": true, + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, + "is-npm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", + "dev": true + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-obj": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-retry-allowed": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", + "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", + "dev": true + }, + "is-root": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-root/-/is-root-1.0.0.tgz", + "integrity": "sha1-B7bCM7w5TNnQK6FclmvWZg1jQtU=", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-svg": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-2.1.0.tgz", + "integrity": "sha1-z2EJDaDZ77yrhyLeum8DIgjbsOk=", + "dev": true, + "requires": { + "html-comment-regex": "^1.1.0" + } + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul-api": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.7.tgz", + "integrity": "sha512-4/ApBnMVeEPG3EkSzcw25wDe4N66wxwn+KKn6b47vyek8Xb3NBAcg4xfuQbS7BqcZuTX4wxfD5lVagdggR3gyA==", + "dev": true, + "requires": { + "async": "^2.1.4", + "fileset": "^2.0.2", + "istanbul-lib-coverage": "^1.2.1", + "istanbul-lib-hook": "^1.2.2", + "istanbul-lib-instrument": "^1.10.2", + "istanbul-lib-report": "^1.1.5", + "istanbul-lib-source-maps": "^1.2.6", + "istanbul-reports": "^1.5.1", + "js-yaml": "^3.7.0", + "mkdirp": "^0.5.1", + "once": "^1.4.0" + } + }, + "istanbul-lib-coverage": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", + "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz", + "integrity": "sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==", + "dev": true, + "requires": { + "append-transform": "^0.4.0" + } + }, + "istanbul-lib-instrument": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", + "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", + "dev": true, + "requires": { + "babel-generator": "^6.18.0", + "babel-template": "^6.16.0", + "babel-traverse": "^6.18.0", + "babel-types": "^6.18.0", + "babylon": "^6.18.0", + "istanbul-lib-coverage": "^1.2.1", + "semver": "^5.3.0" + } + }, + "istanbul-lib-report": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz", + "integrity": "sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^1.2.1", + "mkdirp": "^0.5.1", + "path-parse": "^1.0.5", + "supports-color": "^3.1.2" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz", + "integrity": "sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg==", + "dev": true, + "requires": { + "debug": "^3.1.0", + "istanbul-lib-coverage": "^1.2.1", + "mkdirp": "^0.5.1", + "rimraf": "^2.6.1", + "source-map": "^0.5.3" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "istanbul-reports": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.5.1.tgz", + "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==", + "dev": true, + "requires": { + "handlebars": "^4.0.3" + } + }, + "jest": { + "version": "20.0.4", + "resolved": "https://registry.npmjs.org/jest/-/jest-20.0.4.tgz", + "integrity": "sha1-PdJgwpidba1nix6cxNkZRPbWAqw=", + "dev": true, + "requires": { + "jest-cli": "^20.0.4" + }, + "dependencies": { + "jest-cli": { + "version": "20.0.4", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-20.0.4.tgz", + "integrity": "sha1-5TKxnYiuW8bEF+iwWTpv6VSx3JM=", + "dev": true, + "requires": { + "ansi-escapes": "^1.4.0", + "callsites": "^2.0.0", + "chalk": "^1.1.3", + "graceful-fs": "^4.1.11", + "is-ci": "^1.0.10", + "istanbul-api": "^1.1.1", + "istanbul-lib-coverage": "^1.0.1", + "istanbul-lib-instrument": "^1.4.2", + "istanbul-lib-source-maps": "^1.1.0", + "jest-changed-files": "^20.0.3", + "jest-config": "^20.0.4", + "jest-docblock": "^20.0.3", + "jest-environment-jsdom": "^20.0.3", + "jest-haste-map": "^20.0.4", + "jest-jasmine2": "^20.0.4", + "jest-message-util": "^20.0.3", + "jest-regex-util": "^20.0.3", + "jest-resolve-dependencies": "^20.0.3", + "jest-runtime": "^20.0.4", + "jest-snapshot": "^20.0.3", + "jest-util": "^20.0.3", + "micromatch": "^2.3.11", + "node-notifier": "^5.0.2", + "pify": "^2.3.0", + "slash": "^1.0.0", + "string-length": "^1.0.1", + "throat": "^3.0.0", + "which": "^1.2.12", + "worker-farm": "^1.3.1", + "yargs": "^7.0.2" + } + } + } + }, + "jest-changed-files": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-20.0.3.tgz", + "integrity": "sha1-k5TVzGXEOEBhSb7xv01Sto4D4/g=", + "dev": true + }, + "jest-config": { + "version": "20.0.4", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-20.0.4.tgz", + "integrity": "sha1-43kwqyIXyRNgXv8T5712PsSPruo=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "glob": "^7.1.1", + "jest-environment-jsdom": "^20.0.3", + "jest-environment-node": "^20.0.3", + "jest-jasmine2": "^20.0.4", + "jest-matcher-utils": "^20.0.3", + "jest-regex-util": "^20.0.3", + "jest-resolve": "^20.0.4", + "jest-validate": "^20.0.3", + "pretty-format": "^20.0.3" + } + }, + "jest-diff": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-20.0.3.tgz", + "integrity": "sha1-gfKI/Z5nXw+yPHXxwrGURf5YZhc=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "diff": "^3.2.0", + "jest-matcher-utils": "^20.0.3", + "pretty-format": "^20.0.3" + } + }, + "jest-docblock": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-20.0.3.tgz", + "integrity": "sha1-F76phDQswz2DxQ++FUXqDvqkRxI=", + "dev": true + }, + "jest-environment-jsdom": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-20.0.3.tgz", + "integrity": "sha1-BIqKwS7iJfcZBBdxODS7mZeH3pk=", + "dev": true, + "requires": { + "jest-mock": "^20.0.3", + "jest-util": "^20.0.3", + "jsdom": "^9.12.0" + } + }, + "jest-environment-node": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-20.0.3.tgz", + "integrity": "sha1-1Ii8RhKvLCRumG6K52caCZFj1AM=", + "dev": true, + "requires": { + "jest-mock": "^20.0.3", + "jest-util": "^20.0.3" + } + }, + "jest-get-type": { + "version": "22.4.3", + "resolved": "http://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", + "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", + "dev": true + }, + "jest-haste-map": { + "version": "20.0.5", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-20.0.5.tgz", + "integrity": "sha512-0IKAQjUvuZjMCNi/0VNQQF74/H9KB67hsHJqGiwTWQC6XO5Azs7kLWm+6Q/dwuhvDUvABDOBMFK2/FwZ3sZ07Q==", + "dev": true, + "requires": { + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.1.11", + "jest-docblock": "^20.0.3", + "micromatch": "^2.3.11", + "sane": "~1.6.0", + "worker-farm": "^1.3.1" + } + }, + "jest-jasmine2": { + "version": "20.0.4", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-20.0.4.tgz", + "integrity": "sha1-/MWxQReA2RHQQpAu8YWehS5g1eE=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "graceful-fs": "^4.1.11", + "jest-diff": "^20.0.3", + "jest-matcher-utils": "^20.0.3", + "jest-matchers": "^20.0.3", + "jest-message-util": "^20.0.3", + "jest-snapshot": "^20.0.3", + "once": "^1.4.0", + "p-map": "^1.1.1" + } + }, + "jest-matcher-utils": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-20.0.3.tgz", + "integrity": "sha1-s6a443yld4A7CDKpixZPRLeBVhI=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "pretty-format": "^20.0.3" + } + }, + "jest-matchers": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jest-matchers/-/jest-matchers-20.0.3.tgz", + "integrity": "sha1-ymnbHDLbWm9wf6XgQBq7VXAN/WA=", + "dev": true, + "requires": { + "jest-diff": "^20.0.3", + "jest-matcher-utils": "^20.0.3", + "jest-message-util": "^20.0.3", + "jest-regex-util": "^20.0.3" + } + }, + "jest-message-util": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-20.0.3.tgz", + "integrity": "sha1-auwoRDBvyw5udNV5bBAG2W/dgxw=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "micromatch": "^2.3.11", + "slash": "^1.0.0" + } + }, + "jest-mock": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-20.0.3.tgz", + "integrity": "sha1-i8Bw6QQUqhVcEajWTIaaDVxx2lk=", + "dev": true + }, + "jest-regex-util": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-20.0.3.tgz", + "integrity": "sha1-hburXRM+RGJbGfr4xqpRItCF12I=", + "dev": true + }, + "jest-resolve": { + "version": "20.0.4", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-20.0.4.tgz", + "integrity": "sha1-lEiz6La6/BVHlETGSZBFt//ll6U=", + "dev": true, + "requires": { + "browser-resolve": "^1.11.2", + "is-builtin-module": "^1.0.0", + "resolve": "^1.3.2" + } + }, + "jest-resolve-dependencies": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-20.0.3.tgz", + "integrity": "sha1-bhSntxevDyyzZnxUneQK8Bexcjo=", + "dev": true, + "requires": { + "jest-regex-util": "^20.0.3" + } + }, + "jest-runtime": { + "version": "20.0.4", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-20.0.4.tgz", + "integrity": "sha1-osgCIZxCA/dU3xQE5JAYYWnRJNg=", + "dev": true, + "requires": { + "babel-core": "^6.0.0", + "babel-jest": "^20.0.3", + "babel-plugin-istanbul": "^4.0.0", + "chalk": "^1.1.3", + "convert-source-map": "^1.4.0", + "graceful-fs": "^4.1.11", + "jest-config": "^20.0.4", + "jest-haste-map": "^20.0.4", + "jest-regex-util": "^20.0.3", + "jest-resolve": "^20.0.4", + "jest-util": "^20.0.3", + "json-stable-stringify": "^1.0.1", + "micromatch": "^2.3.11", + "strip-bom": "3.0.0", + "yargs": "^7.0.2" + }, + "dependencies": { + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, + "jest-snapshot": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-20.0.3.tgz", + "integrity": "sha1-W4R+GtsaTZCFKn+fElCG4YfHZWY=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "jest-diff": "^20.0.3", + "jest-matcher-utils": "^20.0.3", + "jest-util": "^20.0.3", + "natural-compare": "^1.4.0", + "pretty-format": "^20.0.3" + } + }, + "jest-util": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-20.0.3.tgz", + "integrity": "sha1-DAf32A2C9OWmfG+LnD/n9lz9Mq0=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "graceful-fs": "^4.1.11", + "jest-message-util": "^20.0.3", + "jest-mock": "^20.0.3", + "jest-validate": "^20.0.3", + "leven": "^2.1.0", + "mkdirp": "^0.5.1" + } + }, + "jest-validate": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-20.0.3.tgz", + "integrity": "sha1-0M/R3k9XnymEhJJcKA+PHZTsPKs=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "jest-matcher-utils": "^20.0.3", + "leven": "^2.1.0", + "pretty-format": "^20.0.3" + } + }, + "js-base64": { + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.9.tgz", + "integrity": "sha512-xcinL3AuDJk7VSzsHgb9DvvIXayBbadtMZ4HFPx8rUszbW1MuNMlwYVC4zzCZ6e1sqZpnNS5ZFYOhXqA39T7LQ==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz", + "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^2.6.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsdom": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-9.12.0.tgz", + "integrity": "sha1-6MVG//ywbADUgzyoRBD+1/igl9Q=", + "dev": true, + "requires": { + "abab": "^1.0.3", + "acorn": "^4.0.4", + "acorn-globals": "^3.1.0", + "array-equal": "^1.0.0", + "content-type-parser": "^1.0.1", + "cssom": ">= 0.3.2 < 0.4.0", + "cssstyle": ">= 0.2.37 < 0.3.0", + "escodegen": "^1.6.1", + "html-encoding-sniffer": "^1.0.1", + "nwmatcher": ">= 1.3.9 < 2.0.0", + "parse5": "^1.5.1", + "request": "^2.79.0", + "sax": "^1.2.1", + "symbol-tree": "^3.2.1", + "tough-cookie": "^2.3.2", + "webidl-conversions": "^4.0.0", + "whatwg-encoding": "^1.0.1", + "whatwg-url": "^4.3.0", + "xml-name-validator": "^2.0.1" + } + }, + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + }, + "json-loader": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", + "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "~0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "json5": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "jsonfile": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", + "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "killable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", + "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "klaw": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, + "latest-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", + "dev": true, + "requires": { + "package-json": "^4.0.0" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "left-pad": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", + "dev": true + }, + "leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "loader-runner": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.1.tgz", + "integrity": "sha512-By6ZFY7ETWOc9RFaAIb23IjJVcM4dvJC/N57nmdz9RSkMXvAXGI7SyVlAw3v8vjtDRlqThgVDVmTnr9fqMlxkw==", + "dev": true + }, + "loader-utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", + "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", + "dev": true, + "requires": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "dev": true + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", + "dev": true + }, + "lodash.endswith": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.endswith/-/lodash.endswith-4.2.1.tgz", + "integrity": "sha1-/tWawXOO0+I27dcGTsRWRIs3vAk=", + "dev": true + }, + "lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "dev": true + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", + "dev": true + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "dev": true + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, + "lodash.startswith": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.startswith/-/lodash.startswith-4.2.1.tgz", + "integrity": "sha1-xZjErc4YiiflMUVzHNxsDnF3YAw=", + "dev": true + }, + "lodash.template": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.4.0.tgz", + "integrity": "sha1-5zoDhcg1VZF0bgILmWecaQ5o+6A=", + "dev": true, + "requires": { + "lodash._reinterpolate": "~3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "lodash.templatesettings": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz", + "integrity": "sha1-K01OlbpEDZFf8IvImeRVNmZxMxY=", + "dev": true, + "requires": { + "lodash._reinterpolate": "~3.0.0" + } + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true + }, + "loglevel": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz", + "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=", + "dev": true + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", + "dev": true + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + }, + "lru-cache": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "dev": true, + "requires": { + "tmpl": "1.0.x" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "math-expression-evaluator": { + "version": "1.2.17", + "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz", + "integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw=", + "dev": true + }, + "math-random": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", + "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=", + "dev": true + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "mem": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "meow": { + "version": "3.7.0", + "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "merge": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz", + "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", + "dev": true + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "requires": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", + "dev": true + }, + "mime-types": { + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "dev": true, + "requires": { + "mime-db": "~1.37.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "minify-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/minify-json/-/minify-json-1.0.0.tgz", + "integrity": "sha1-tVufoZmNViO4za/L2+UQQvs+TGw=", + "dev": true, + "requires": { + "bluebird": "^3.5.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" + } + }, + "mississippi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", + "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^2.0.1", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "move-cli": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/move-cli/-/move-cli-1.2.0.tgz", + "integrity": "sha512-P6L5mOzY0qWGa7KnwOBn7idJOlMnB5GXFX5Qmhq1i2q9j4rt4ruTHbrvR0xRADws6prJchYmji0IQeZDwX2zVg==", + "dev": true, + "requires": { + "meow": "^5.0.0", + "mv": "^2.1.1", + "update-notifier": "^2.5.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + } + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", + "dev": true + }, + "meow": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", + "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", + "dev": true, + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0", + "yargs-parser": "^10.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + } + }, + "redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "dev": true, + "requires": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "dev": true + }, + "trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "dev": true + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "dev": true, + "requires": { + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" + } + }, + "multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "mv": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", + "dev": true, + "requires": { + "mkdirp": "~0.5.1", + "ncp": "~2.0.0", + "rimraf": "~2.4.0" + }, + "dependencies": { + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "rimraf": { + "version": "2.4.5", + "resolved": "http://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", + "dev": true, + "requires": { + "glob": "^6.0.1" + } + } + } + }, + "nan": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", + "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "ncp": { + "version": "2.0.0", + "resolved": "http://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", + "dev": true + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true + }, + "neo-async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", + "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==", + "dev": true + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "requires": { + "lower-case": "^1.1.1" + } + }, + "node-forge": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", + "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==", + "dev": true + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true + }, + "node-libs-browser": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", + "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", + "dev": true, + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^1.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.0", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.10.3", + "vm-browserify": "0.0.4" + } + }, + "node-notifier": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.3.0.tgz", + "integrity": "sha512-AhENzCSGZnZJgBARsUjnQ7DnZbzyP+HxlVXuD0xqAnvL8q+OqtSX7lGg9e8nHzwXkMMXNdVeqq4E2M3EUAqX6Q==", + "dev": true, + "requires": { + "growly": "^1.3.0", + "semver": "^5.5.0", + "shellwords": "^0.1.1", + "which": "^1.3.0" + } + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true + }, + "normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", + "dev": true, + "requires": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "nwmatcher": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.4.tgz", + "integrity": "sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ==", + "dev": true + }, + "nwsapi": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.0.9.tgz", + "integrity": "sha512-nlWFSCTYQcHk/6A9FFnfhKc14c3aFhfdNBXgo8Qgi9QTBu/qg3Ww+Uiz9wMzXd1T8GFxPc2QIHB6Qtf2XFryFQ==", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "object-keys": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "requires": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", + "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "opn": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.2.0.tgz", + "integrity": "sha512-Jd/GpzPyHF4P2/aNOVmS3lfMSWV9J7cOhCG1s08XCEAsPkB7lp6ddiU0J7XzyQRDUh8BqJ7PchfINjR8jyofRQ==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + }, + "dependencies": { + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + } + } + }, + "original": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", + "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", + "dev": true, + "requires": { + "url-parse": "^1.4.3" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-locale": { + "version": "1.4.0", + "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "requires": { + "lcid": "^1.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", + "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", + "dev": true + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "package-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", + "dev": true, + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, + "pako": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", + "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", + "dev": true + }, + "parallel-transform": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", + "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", + "dev": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "dev": true, + "requires": { + "no-case": "^2.2.0" + } + }, + "parse-asn1": { + "version": "5.1.1", + "resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", + "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", + "dev": true, + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3" + } + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dev": true, + "requires": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "parse5": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz", + "integrity": "sha1-m387DeMr543CQBsXVzzK8Pb1nZQ=", + "dev": true + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, + "pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", + "dev": true + }, + "portfinder": { + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.18.tgz", + "integrity": "sha512-KanzLOERzKoX3En5yTiV8K/arnU1ykYVokmtEn0PgCzqKZG9489tqW8ifp9+v3/VJZ5YDjvDt/PAP5WaPgk7FA==", + "dev": true, + "requires": { + "async": "^1.5.2", + "debug": "^2.2.0", + "mkdirp": "0.5.x" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "postcss-calc": { + "version": "5.3.1", + "resolved": "http://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz", + "integrity": "sha1-d7rnypKK2FcW4v2kLyYb98HWW14=", + "dev": true, + "requires": { + "postcss": "^5.0.2", + "postcss-message-helpers": "^2.0.0", + "reduce-css-calc": "^1.2.6" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-colormin": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-2.2.2.tgz", + "integrity": "sha1-ZjFBfV8OkJo9fsJrJMio0eT5bks=", + "dev": true, + "requires": { + "colormin": "^1.0.5", + "postcss": "^5.0.13", + "postcss-value-parser": "^3.2.3" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-convert-values": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz", + "integrity": "sha1-u9hZPFwf0uPRwyK7kl3K6Nrk1i0=", + "dev": true, + "requires": { + "postcss": "^5.0.11", + "postcss-value-parser": "^3.1.2" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-discard-comments": { + "version": "2.0.4", + "resolved": "http://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz", + "integrity": "sha1-vv6J+v1bPazlzM5Rt2uBUUvgDj0=", + "dev": true, + "requires": { + "postcss": "^5.0.14" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-discard-duplicates": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz", + "integrity": "sha1-uavye4isGIFYpesSq8riAmO5GTI=", + "dev": true, + "requires": { + "postcss": "^5.0.4" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-discard-empty": { + "version": "2.1.0", + "resolved": "http://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz", + "integrity": "sha1-0rS9nVztXr2Nyt52QMfXzX9PkrU=", + "dev": true, + "requires": { + "postcss": "^5.0.14" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-discard-overridden": { + "version": "0.1.1", + "resolved": "http://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz", + "integrity": "sha1-ix6vVU9ob7KIzYdMVWZ7CqNmjVg=", + "dev": true, + "requires": { + "postcss": "^5.0.16" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-discard-unused": { + "version": "2.2.3", + "resolved": "http://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", + "integrity": "sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM=", + "dev": true, + "requires": { + "postcss": "^5.0.14", + "uniqs": "^2.0.0" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-filter-plugins": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/postcss-filter-plugins/-/postcss-filter-plugins-2.0.3.tgz", + "integrity": "sha512-T53GVFsdinJhgwm7rg1BzbeBRomOg9y5MBVhGcsV0CxurUdVj1UlPdKtn7aqYA/c/QVkzKMjq2bSV5dKG5+AwQ==", + "dev": true, + "requires": { + "postcss": "^5.0.4" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-flexbugs-fixes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-3.2.0.tgz", + "integrity": "sha512-0AuD9HG1Ey3/3nqPWu9yqf7rL0KCPu5VgjDsjf5mzEcuo9H/z8nco/fljKgjsOUrZypa95MI0kS4xBZeBzz2lw==", + "dev": true, + "requires": { + "postcss": "^6.0.1" + } + }, + "postcss-load-config": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-1.2.0.tgz", + "integrity": "sha1-U56a/J3chiASHr+djDZz4M5Q0oo=", + "dev": true, + "requires": { + "cosmiconfig": "^2.1.0", + "object-assign": "^4.1.0", + "postcss-load-options": "^1.2.0", + "postcss-load-plugins": "^2.3.0" + } + }, + "postcss-load-options": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postcss-load-options/-/postcss-load-options-1.2.0.tgz", + "integrity": "sha1-sJixVZ3awt8EvAuzdfmaXP4rbYw=", + "dev": true, + "requires": { + "cosmiconfig": "^2.1.0", + "object-assign": "^4.1.0" + } + }, + "postcss-load-plugins": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/postcss-load-plugins/-/postcss-load-plugins-2.3.0.tgz", + "integrity": "sha1-dFdoEWWZrKLwCfrUJrABdQSdjZI=", + "dev": true, + "requires": { + "cosmiconfig": "^2.1.1", + "object-assign": "^4.1.0" + } + }, + "postcss-loader": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-2.0.8.tgz", + "integrity": "sha512-KtXBiQ/r/WYW8LxTSJK7h8wLqvCMSub/BqmRnud/Mu8RzwflW9cmXxwsMwbn15TNv287Hcufdb3ZSs7xHKnG8Q==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "postcss": "^6.0.0", + "postcss-load-config": "^1.2.0", + "schema-utils": "^0.3.0" + } + }, + "postcss-merge-idents": { + "version": "2.1.7", + "resolved": "http://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz", + "integrity": "sha1-TFUwMTwI4dWzu/PSu8dH4njuonA=", + "dev": true, + "requires": { + "has": "^1.0.1", + "postcss": "^5.0.10", + "postcss-value-parser": "^3.1.1" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-merge-longhand": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz", + "integrity": "sha1-I9kM0Sewp3mUkVMyc5A0oaTz1lg=", + "dev": true, + "requires": { + "postcss": "^5.0.4" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-merge-rules": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz", + "integrity": "sha1-0d9d+qexrMO+VT8OnhDofGG19yE=", + "dev": true, + "requires": { + "browserslist": "^1.5.2", + "caniuse-api": "^1.5.2", + "postcss": "^5.0.4", + "postcss-selector-parser": "^2.2.2", + "vendors": "^1.0.0" + }, + "dependencies": { + "browserslist": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", + "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", + "dev": true, + "requires": { + "caniuse-db": "^1.0.30000639", + "electron-to-chromium": "^1.2.7" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-message-helpers": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz", + "integrity": "sha1-pPL0+rbk/gAvCu0ABHjN9S+bpg4=", + "dev": true + }, + "postcss-minify-font-values": { + "version": "1.0.5", + "resolved": "http://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz", + "integrity": "sha1-S1jttWZB66fIR0qzUmyv17vey2k=", + "dev": true, + "requires": { + "object-assign": "^4.0.1", + "postcss": "^5.0.4", + "postcss-value-parser": "^3.0.2" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-minify-gradients": { + "version": "1.0.5", + "resolved": "http://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz", + "integrity": "sha1-Xb2hE3NwP4PPtKPqOIHY11/15uE=", + "dev": true, + "requires": { + "postcss": "^5.0.12", + "postcss-value-parser": "^3.3.0" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-minify-params": { + "version": "1.2.2", + "resolved": "http://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz", + "integrity": "sha1-rSzgcTc7lDs9kwo/pZo1jCjW8fM=", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.1", + "postcss": "^5.0.2", + "postcss-value-parser": "^3.0.2", + "uniqs": "^2.0.0" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-minify-selectors": { + "version": "2.1.1", + "resolved": "http://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz", + "integrity": "sha1-ssapjAByz5G5MtGkllCBFDEXNb8=", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.2", + "has": "^1.0.1", + "postcss": "^5.0.14", + "postcss-selector-parser": "^2.0.0" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-modules-extract-imports": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz", + "integrity": "sha1-thTJcgvmgW6u41+zpfqh26agXds=", + "dev": true, + "requires": { + "postcss": "^6.0.1" + } + }, + "postcss-modules-local-by-default": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", + "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", + "dev": true, + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + } + }, + "postcss-modules-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", + "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", + "dev": true, + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + } + }, + "postcss-modules-values": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", + "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", + "dev": true, + "requires": { + "icss-replace-symbols": "^1.1.0", + "postcss": "^6.0.1" + } + }, + "postcss-normalize-charset": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz", + "integrity": "sha1-757nEhLX/nWceO0WL2HtYrXLk/E=", + "dev": true, + "requires": { + "postcss": "^5.0.5" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-normalize-url": { + "version": "3.0.8", + "resolved": "http://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz", + "integrity": "sha1-EI90s/L82viRov+j6kWSJ5/HgiI=", + "dev": true, + "requires": { + "is-absolute-url": "^2.0.0", + "normalize-url": "^1.4.0", + "postcss": "^5.0.14", + "postcss-value-parser": "^3.2.3" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-ordered-values": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz", + "integrity": "sha1-7sbCpntsQSqNsgQud/6NpD+VwR0=", + "dev": true, + "requires": { + "postcss": "^5.0.4", + "postcss-value-parser": "^3.0.1" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-reduce-idents": { + "version": "2.4.0", + "resolved": "http://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz", + "integrity": "sha1-wsbSDMlYKE9qv75j92Cb9AkFmtM=", + "dev": true, + "requires": { + "postcss": "^5.0.4", + "postcss-value-parser": "^3.0.2" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-reduce-initial": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz", + "integrity": "sha1-aPgGlfBF0IJjqHmtJA343WT2ROo=", + "dev": true, + "requires": { + "postcss": "^5.0.4" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-reduce-transforms": { + "version": "1.0.4", + "resolved": "http://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz", + "integrity": "sha1-/3b02CEkN7McKYpC0uFEQCV3GuE=", + "dev": true, + "requires": { + "has": "^1.0.1", + "postcss": "^5.0.8", + "postcss-value-parser": "^3.0.1" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-selector-parser": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz", + "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=", + "dev": true, + "requires": { + "flatten": "^1.0.2", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "postcss-svgo": { + "version": "2.1.6", + "resolved": "http://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.6.tgz", + "integrity": "sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0=", + "dev": true, + "requires": { + "is-svg": "^2.0.0", + "postcss": "^5.0.14", + "postcss-value-parser": "^3.2.3", + "svgo": "^0.7.0" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-unique-selectors": { + "version": "2.0.2", + "resolved": "http://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz", + "integrity": "sha1-mB1X0p3csz57Hf4f1DuGSfkzyh0=", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.1", + "postcss": "^5.0.4", + "uniqs": "^2.0.0" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "postcss-zindex": { + "version": "2.2.0", + "resolved": "http://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.2.0.tgz", + "integrity": "sha1-0hCd3AVbka9n/EyzsCWUZjnSryI=", + "dev": true, + "requires": { + "has": "^1.0.1", + "postcss": "^5.0.4", + "uniqs": "^2.0.0" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true + }, + "pretty-bytes": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-4.0.2.tgz", + "integrity": "sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk=", + "dev": true + }, + "pretty-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", + "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", + "dev": true, + "requires": { + "renderkid": "^2.0.1", + "utila": "~0.4" + } + }, + "pretty-format": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-20.0.3.tgz", + "integrity": "sha1-Ag41ClYKH+GpjcO+tsz/s4beixQ=", + "dev": true, + "requires": { + "ansi-regex": "^2.1.1", + "ansi-styles": "^3.0.0" + } + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "promise": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.0.1.tgz", + "integrity": "sha1-5F1osAoXZHttpxG/he1u1HII9FA=", + "dev": true, + "requires": { + "asap": "~2.0.3" + } + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "prop-types": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", + "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "requires": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + }, + "proxy-addr": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", + "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", + "dev": true, + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.8.0" + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "psl": { + "version": "1.1.29", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", + "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==", + "dev": true + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", + "dev": true, + "requires": { + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "querystringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.0.tgz", + "integrity": "sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==", + "dev": true + }, + "quick-lru": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", + "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", + "dev": true + }, + "raf": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.0.tgz", + "integrity": "sha512-pDP/NMRAXoTfrhCfyfSEwJAKLaxBU9eApMeBPB1TkDouZmvPerIClV8lTAd+uF8ZiTaVl69e1FCxQrAd/VTjGw==", + "dev": true, + "requires": { + "performance-now": "^2.1.0" + } + }, + "randomatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.0.tgz", + "integrity": "sha512-KnGPVE0lo2WoXxIZ7cPR8YBpiol4gsSuOwDSg410oHh80ZMp5EiypNqL2K4Z77vJn6lB5rap7IkAmcUlalcnBQ==", + "dev": true, + "requires": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "randombytes": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", + "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "dev": true + }, + "raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "dev": true, + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "react": { + "version": "16.6.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.6.0.tgz", + "integrity": "sha512-zJPnx/jKtuOEXCbQ9BKaxDMxR0001/hzxXwYxG8septeyYGfsgAei6NgfbVgOhbY1WOP2o3VPs/E9HaN+9hV3Q==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.10.0" + } + }, + "react-dev-utils": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-5.0.3.tgz", + "integrity": "sha512-Mvs6ofsc2xTjeZIrMaIfbXfsPVrbdVy/cVqq6SAacnqfMlcBpDuivhWZ1ODGeJ8HgmyWTLH971PYjj/EPCDVAw==", + "dev": true, + "requires": { + "address": "1.0.3", + "babel-code-frame": "6.26.0", + "chalk": "1.1.3", + "cross-spawn": "5.1.0", + "detect-port-alt": "1.1.6", + "escape-string-regexp": "1.0.5", + "filesize": "3.5.11", + "global-modules": "1.0.0", + "gzip-size": "3.0.0", + "inquirer": "3.3.0", + "is-root": "1.0.0", + "opn": "5.2.0", + "react-error-overlay": "^4.0.1", + "recursive-readdir": "2.2.1", + "shell-quote": "1.6.1", + "sockjs-client": "1.1.5", + "strip-ansi": "3.0.1", + "text-table": "0.2.0" + } + }, + "react-dom": { + "version": "16.5.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.5.2.tgz", + "integrity": "sha512-RC8LDw8feuZOHVgzEf7f+cxBr/DnKdqp56VU0lAs1f4UfKc4cU8wU4fTq/mgnvynLQo8OtlPC19NUFh/zjZPuA==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "schedule": "^0.5.0" + } + }, + "react-error-overlay": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-4.0.1.tgz", + "integrity": "sha512-xXUbDAZkU08aAkjtUvldqbvI04ogv+a1XdHxvYuHPYKIVk/42BIOD0zSKTHAWV4+gDy3yGm283z2072rA2gdtw==", + "dev": true + }, + "react-scripts-ts": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/react-scripts-ts/-/react-scripts-ts-3.1.0.tgz", + "integrity": "sha512-D5MobGSGswNH3e8RVa8M50jIsazNIqizO5qwWYqfo/tMYSvG0ENotzD3SYMUWYb1E5fIcM+D9a0XEJgo3VgtCQ==", + "dev": true, + "requires": { + "autoprefixer": "7.1.6", + "babel-jest": "20.0.3", + "babel-loader": "7.1.2", + "babel-preset-react-app": "^3.1.2", + "case-sensitive-paths-webpack-plugin": "2.1.1", + "chalk": "1.1.3", + "css-loader": "0.28.7", + "dotenv": "4.0.0", + "dotenv-expand": "4.2.0", + "extract-text-webpack-plugin": "3.0.2", + "file-loader": "1.1.5", + "fork-ts-checker-webpack-plugin": "^0.2.8", + "fs-extra": "3.0.1", + "fsevents": "^1.1.3", + "html-webpack-plugin": "2.29.0", + "jest": "20.0.4", + "object-assign": "4.1.1", + "postcss-flexbugs-fixes": "3.2.0", + "postcss-loader": "2.0.8", + "promise": "8.0.1", + "raf": "3.4.0", + "react-dev-utils": "^5.0.2", + "resolve": "1.6.0", + "source-map-loader": "^0.2.1", + "style-loader": "0.19.0", + "sw-precache-webpack-plugin": "0.11.4", + "ts-jest": "22.0.1", + "ts-loader": "^2.3.7", + "tsconfig-paths-webpack-plugin": "^2.0.0", + "tslint": "^5.7.0", + "tslint-config-prettier": "^1.10.0", + "tslint-react": "^3.2.0", + "uglifyjs-webpack-plugin": "1.2.5", + "url-loader": "0.6.2", + "webpack": "3.8.1", + "webpack-dev-server": "2.11.3", + "webpack-manifest-plugin": "1.3.2", + "whatwg-fetch": "2.0.3" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + } + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + } + } + }, + "recursive-readdir": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.1.tgz", + "integrity": "sha1-kO8jHQd4xc4JPJpI105cVCLROpk=", + "dev": true, + "requires": { + "minimatch": "3.0.3" + }, + "dependencies": { + "minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=", + "dev": true, + "requires": { + "brace-expansion": "^1.0.0" + } + } + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "reduce-css-calc": { + "version": "1.3.0", + "resolved": "http://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", + "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", + "dev": true, + "requires": { + "balanced-match": "^0.4.2", + "math-expression-evaluator": "^1.2.14", + "reduce-function-call": "^1.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "dev": true + } + } + }, + "reduce-function-call": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.2.tgz", + "integrity": "sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk=", + "dev": true, + "requires": { + "balanced-match": "^0.4.2" + }, + "dependencies": { + "balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "dev": true + } + } + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + }, + "regenerator-transform": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", + "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", + "dev": true, + "requires": { + "babel-runtime": "^6.18.0", + "babel-types": "^6.19.0", + "private": "^0.1.6" + } + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "dev": true, + "requires": { + "is-equal-shallow": "^0.1.3" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpu-core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", + "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", + "dev": true, + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "dev": true, + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "dev": true, + "requires": { + "rc": "^1.0.1" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "renderkid": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.2.tgz", + "integrity": "sha512-FsygIxevi1jSiPY9h7vZmBFUbAOcbYm9UwyiLNdVsLRs/5We9Ob5NMPbGYUTWiLq5L+ezlVdE0A8bbME5CWTpg==", + "dev": true, + "requires": { + "css-select": "^1.1.0", + "dom-converter": "~0.2", + "htmlparser2": "~3.3.0", + "strip-ansi": "^3.0.0", + "utila": "^0.4.0" + } + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "request-promise-core": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", + "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", + "dev": true, + "requires": { + "lodash": "^4.13.1" + } + }, + "request-promise-native": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz", + "integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=", + "dev": true, + "requires": { + "request-promise-core": "1.1.1", + "stealthy-require": "^1.1.0", + "tough-cookie": ">=2.3.3" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-from-string": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz", + "integrity": "sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.6.0.tgz", + "integrity": "sha512-mw7JQNu5ExIkcw4LPih0owX/TZXjD/ZUF/ZQ/pDnkw3ZKhDcZZw5klmBlj6gVMwjQ3Pz5Jgu7F3d0jcDVuEWdw==", + "dev": true, + "requires": { + "path-parse": "^1.0.5" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "requires": { + "align-text": "^0.1.1" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "^7.0.5" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "dev": true, + "requires": { + "aproba": "^1.1.1" + } + }, + "rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "dev": true + }, + "rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "dev": true, + "requires": { + "rx-lite": "*" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sane": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-1.6.0.tgz", + "integrity": "sha1-lhDEUjB6E10pwf3+JUcDQYDEZ3U=", + "dev": true, + "requires": { + "anymatch": "^1.3.0", + "exec-sh": "^0.2.0", + "fb-watchman": "^1.8.0", + "minimatch": "^3.0.2", + "minimist": "^1.1.1", + "walker": "~1.0.5", + "watch": "~0.10.0" + }, + "dependencies": { + "bser": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bser/-/bser-1.0.2.tgz", + "integrity": "sha1-OBEWlwsqbe6lZG3RXdcnhES1YWk=", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "fb-watchman": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-1.9.2.tgz", + "integrity": "sha1-okz0eCf4LTj7Waaa1wt247auc4M=", + "dev": true, + "requires": { + "bser": "1.0.2" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "schedule": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/schedule/-/schedule-0.5.0.tgz", + "integrity": "sha512-HUcJicG5Ou8xfR//c2rPT0lPIRR09vVvN81T9fqfVgBmhERUbDEQoYKjpBxbueJnCPpSu2ujXzOnRQt6x9o/jw==", + "requires": { + "object-assign": "^4.1.1" + } + }, + "scheduler": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.10.0.tgz", + "integrity": "sha512-+TSTVTCBAA3h8Anei3haDc1IRwMeDmtI/y/o3iBe3Mjl2vwYF9DtPDt929HyRmV/e7au7CLu8sc4C4W0VOs29w==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "schema-utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz", + "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", + "dev": true, + "requires": { + "ajv": "^5.0.0" + } + }, + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "dev": true + }, + "selfsigned": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.4.tgz", + "integrity": "sha512-9AukTiDmHXGXWtWjembZ5NDmVvP2695EtpgbCsxCa68w3c88B+alqbmZ4O3hZ4VWGXeGWzEVdvqgAJD8DQPCDw==", + "dev": true, + "requires": { + "node-forge": "0.7.5" + } + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, + "semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "dev": true, + "requires": { + "semver": "^5.0.3" + } + }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "dependencies": { + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.5.0.tgz", + "integrity": "sha512-Ga8c8NjAAp46Br4+0oZ2WxJCwIzwP60Gq1YPgU+39PiTVxyed/iKE/zyZI6+UlVYH5Q4PaQdHhcegIFPZTUfoQ==", + "dev": true + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + } + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + } + }, + "serviceworker-cache-polyfill": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serviceworker-cache-polyfill/-/serviceworker-cache-polyfill-4.0.0.tgz", + "integrity": "sha1-3hnuc77yGrPAdAo3sz22JGS6ves=", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "shell-quote": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", + "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=", + "dev": true, + "requires": { + "array-filter": "~0.0.0", + "array-map": "~0.0.0", + "array-reduce": "~0.0.0", + "jsonify": "~0.0.0" + } + }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + } + }, + "sockjs": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", + "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", + "dev": true, + "requires": { + "faye-websocket": "^0.10.0", + "uuid": "^3.0.1" + }, + "dependencies": { + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + } + } + }, + "sockjs-client": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.1.5.tgz", + "integrity": "sha1-G7fA9yIsQPQq3xT0RCy9Eml3GoM=", + "dev": true, + "requires": { + "debug": "^2.6.6", + "eventsource": "0.1.6", + "faye-websocket": "~0.11.0", + "inherits": "^2.0.1", + "json3": "^3.3.2", + "url-parse": "^1.1.8" + } + }, + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "dev": true, + "requires": { + "is-plain-obj": "^1.0.0" + } + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-loader": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.4.tgz", + "integrity": "sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ==", + "dev": true, + "requires": { + "async": "^2.5.0", + "loader-utils": "^1.1.0" + } + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "spdx-correct": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.2.tgz", + "integrity": "sha512-q9hedtzyXHr5S0A1vEPoK/7l8NpfkFYTq6iCY+Pno2ZbdZR6WexZFtqeVGkGxW3TEJMN914Z55EnAGMmenlIQQ==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.1.tgz", + "integrity": "sha512-TfOfPcYGBB5sDuPn3deByxPhmfegAhpDYKSOXZQN81Oyrrif8ZCodOLzK3AesELnCx03kikhyDwh0pfvvQvF8w==", + "dev": true + }, + "spdy": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-3.4.7.tgz", + "integrity": "sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw=", + "dev": true, + "requires": { + "debug": "^2.6.8", + "handle-thing": "^1.2.5", + "http-deceiver": "^1.2.7", + "safe-buffer": "^5.0.1", + "select-hose": "^2.0.0", + "spdy-transport": "^2.0.18" + } + }, + "spdy-transport": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.1.0.tgz", + "integrity": "sha512-bpUeGpZcmZ692rrTiqf9/2EUakI6/kXX1Rpe0ib/DyOzbiexVfXkw6GnvI9hVGvIwVaUhkaBojjCZwLNRGQg1g==", + "dev": true, + "requires": { + "debug": "^2.6.8", + "detect-node": "^2.0.3", + "hpack.js": "^2.1.6", + "obuf": "^1.1.1", + "readable-stream": "^2.2.9", + "safe-buffer": "^5.0.1", + "wbuf": "^1.7.2" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.1.tgz", + "integrity": "sha512-mSdgNUaidk+dRU5MhYtN9zebdzF2iG0cNPWy8HG+W8y+fT1JnSkh0fzzpjOa0L7P8i1Rscz38t0h4gPcKz43xA==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", + "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.1" + } + }, + "stack-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.1.tgz", + "integrity": "sha1-1PM6tU6OOHeLDKXP07OvsS22hiA=", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, + "stream-browserify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", + "dev": true + }, + "string-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", + "integrity": "sha1-VpcPscOFWOnnC3KL894mmsRa36w=", + "dev": true, + "requires": { + "strip-ansi": "^3.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "style-loader": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.19.0.tgz", + "integrity": "sha512-9mx9sC9nX1dgP96MZOODpGC6l1RzQBITI2D5WJhu+wnbrSYVKLGuy14XJSLVQih/0GFrPpjelt+s//VcZQ2Evw==", + "dev": true, + "requires": { + "loader-utils": "^1.0.2", + "schema-utils": "^0.3.0" + } + }, + "subarg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", + "dev": true, + "requires": { + "minimist": "^1.1.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "svgo": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz", + "integrity": "sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U=", + "dev": true, + "requires": { + "coa": "~1.0.1", + "colors": "~1.1.2", + "csso": "~2.3.1", + "js-yaml": "~3.7.0", + "mkdirp": "~0.5.1", + "sax": "~1.2.1", + "whet.extend": "~0.9.9" + } + }, + "sw-precache": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/sw-precache/-/sw-precache-5.2.1.tgz", + "integrity": "sha512-8FAy+BP/FXE+ILfiVTt+GQJ6UEf4CVHD9OfhzH0JX+3zoy2uFk7Vn9EfXASOtVmmIVbL3jE/W8Z66VgPSZcMhw==", + "dev": true, + "requires": { + "dom-urls": "^1.1.0", + "es6-promise": "^4.0.5", + "glob": "^7.1.1", + "lodash.defaults": "^4.2.0", + "lodash.template": "^4.4.0", + "meow": "^3.7.0", + "mkdirp": "^0.5.1", + "pretty-bytes": "^4.0.2", + "sw-toolbox": "^3.4.0", + "update-notifier": "^2.3.0" + } + }, + "sw-precache-webpack-plugin": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/sw-precache-webpack-plugin/-/sw-precache-webpack-plugin-0.11.4.tgz", + "integrity": "sha1-ppUBflTu1XVVFJOlGdwdqNotxeA=", + "dev": true, + "requires": { + "del": "^2.2.2", + "sw-precache": "^5.1.1", + "uglify-js": "^3.0.13" + } + }, + "sw-toolbox": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/sw-toolbox/-/sw-toolbox-3.6.0.tgz", + "integrity": "sha1-Jt8dHHA0hljk3qKIQxkUm3sxg7U=", + "dev": true, + "requires": { + "path-to-regexp": "^1.0.1", + "serviceworker-cache-polyfill": "^4.0.0" + } + }, + "symbol-tree": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", + "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", + "dev": true + }, + "tapable": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.8.tgz", + "integrity": "sha1-mTcqXJmb8t8WCvwNdL7U9HlIzSI=", + "dev": true + }, + "term-size": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", + "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", + "dev": true, + "requires": { + "execa": "^0.7.0" + } + }, + "test-exclude": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.3.tgz", + "integrity": "sha512-SYbXgY64PT+4GAL2ocI3HwPa4Q4TBKm0cwAVeKOt/Aoc0gSpNRjJX8w0pA1LMKZ3LBmd8pYBqApFNQLII9kavA==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "micromatch": "^2.3.11", + "object-assign": "^4.1.0", + "read-pkg-up": "^1.0.1", + "require-main-filename": "^1.0.1" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "tgstation-server-control-panel": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/tgstation-server-control-panel/-/tgstation-server-control-panel-0.1.5.tgz", + "integrity": "sha512-Eqsf5Rc5LX8bve1ReLB2b7zalIS+eBCTwXXfxKAhAIS5XMZlBoVv3ov/JmMQANqvmgUYp72ZAewBHMFvmHjlHA==", + "requires": { + "react": "^16.5.2" + } + }, + "throat": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-3.2.0.tgz", + "integrity": "sha512-/EY8VpvlqJ+sFtLPeOgc8Pl7kQVOWv0woD87KTXVHPIAE842FGT+rokxIhe8xIUP1cfgrkt0as0vDLjDiMtr8w==", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + } + }, + "thunky": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.3.tgz", + "integrity": "sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==", + "dev": true + }, + "time-stamp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-2.1.0.tgz", + "integrity": "sha512-lJbq6KsFhZJtN3fPUVje1tq/hHsJOKUUcUj/MGCiQR6qWBDcyi5kxL9J7/RnaEChCn0+L/DUN2WvemDrkk4i3Q==", + "dev": true + }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", + "dev": true + }, + "timers-browserify": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", + "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", + "dev": true, + "requires": { + "setimmediate": "^1.0.4" + } + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "tmpl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", + "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", + "dev": true + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + } + } + }, + "toposort": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz", + "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=", + "dev": true + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", + "dev": true + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "ts-jest": { + "version": "22.0.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-22.0.1.tgz", + "integrity": "sha512-bc781gViU95lRZF0kzkHiincwmVu96jbC8MFk2SXUCrSj3Zx8sMC6c6gJnIluVQkm8yYaBl5ucqLnwHNRl5l0Q==", + "dev": true, + "requires": { + "babel-core": "^6.24.1", + "babel-plugin-istanbul": "^4.1.4", + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-preset-jest": "^22.0.1", + "cpx": "^1.5.0", + "fs-extra": "4.0.3", + "jest-config": "^22.0.1", + "pkg-dir": "^2.0.0", + "source-map-support": "^0.5.0", + "yargs": "^10.0.3" + }, + "dependencies": { + "abab": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", + "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==", + "dev": true + }, + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "acorn-globals": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.0.tgz", + "integrity": "sha512-hMtHj3s5RnuhvHPowpBYvJVj3rAar82JiDQHvGs1zO0l10ocX/xEdBShNHTJaboucJUsScghp74pH3s7EnHHQw==", + "dev": true, + "requires": { + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" + }, + "dependencies": { + "acorn": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.2.tgz", + "integrity": "sha512-GXmKIvbrN3TV7aVqAzVFaMW8F8wzVX7voEBRO3bDA64+EX37YSayggRJP5Xig6HYHBkWKpFg9W5gg6orklubhg==", + "dev": true + } + } + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "babel-plugin-jest-hoist": { + "version": "22.4.4", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-22.4.4.tgz", + "integrity": "sha512-DUvGfYaAIlkdnygVIEl0O4Av69NtuQWcrjMOv6DODPuhuGLDnbsARz3AwiiI/EkIMMlxQDUcrZ9yoyJvTNjcVQ==", + "dev": true + }, + "babel-preset-jest": { + "version": "22.4.4", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-22.4.4.tgz", + "integrity": "sha512-+dxMtOFwnSYWfum0NaEc0O03oSdwBsjx4tMSChRDPGwu/4wSY6Q6ANW3wkjKpJzzguaovRs/DODcT4hbSN8yiA==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^22.4.4", + "babel-plugin-syntax-object-rest-spread": "^6.13.0" + } + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "cssstyle": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.1.1.tgz", + "integrity": "sha512-364AI1l/M5TYcFH83JnOH/pSqgaNnKmYgKrm0didZMGKWjQB60dymwWy1rKUgL3J1ffdq9xVi2yGLHdSjjSNog==", + "dev": true, + "requires": { + "cssom": "0.3.x" + } + }, + "fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "jest-config": { + "version": "22.4.4", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-22.4.4.tgz", + "integrity": "sha512-9CKfo1GC4zrXSoMLcNeDvQBfgtqGTB1uP8iDIZ97oB26RCUb886KkKWhVcpyxVDOUxbhN+uzcBCeFe7w+Iem4A==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "glob": "^7.1.1", + "jest-environment-jsdom": "^22.4.1", + "jest-environment-node": "^22.4.1", + "jest-get-type": "^22.1.0", + "jest-jasmine2": "^22.4.4", + "jest-regex-util": "^22.1.0", + "jest-resolve": "^22.4.2", + "jest-util": "^22.4.1", + "jest-validate": "^22.4.4", + "pretty-format": "^22.4.0" + } + }, + "jest-diff": { + "version": "22.4.3", + "resolved": "http://registry.npmjs.org/jest-diff/-/jest-diff-22.4.3.tgz", + "integrity": "sha512-/QqGvCDP5oZOF6PebDuLwrB2BMD8ffJv6TAGAdEVuDx1+uEgrHpSFrfrOiMRx2eJ1hgNjlQrOQEHetVwij90KA==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "diff": "^3.2.0", + "jest-get-type": "^22.4.3", + "pretty-format": "^22.4.3" + } + }, + "jest-environment-jsdom": { + "version": "22.4.3", + "resolved": "http://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-22.4.3.tgz", + "integrity": "sha512-FviwfR+VyT3Datf13+ULjIMO5CSeajlayhhYQwpzgunswoaLIPutdbrnfUHEMyJCwvqQFaVtTmn9+Y8WCt6n1w==", + "dev": true, + "requires": { + "jest-mock": "^22.4.3", + "jest-util": "^22.4.3", + "jsdom": "^11.5.1" + } + }, + "jest-environment-node": { + "version": "22.4.3", + "resolved": "http://registry.npmjs.org/jest-environment-node/-/jest-environment-node-22.4.3.tgz", + "integrity": "sha512-reZl8XF6t/lMEuPWwo9OLfttyC26A5AMgDyEQ6DBgZuyfyeNUzYT8BFo6uxCCP/Av/b7eb9fTi3sIHFPBzmlRA==", + "dev": true, + "requires": { + "jest-mock": "^22.4.3", + "jest-util": "^22.4.3" + } + }, + "jest-jasmine2": { + "version": "22.4.4", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-22.4.4.tgz", + "integrity": "sha512-nK3vdUl50MuH7vj/8at7EQVjPGWCi3d5+6aCi7Gxy/XMWdOdbH1qtO/LjKbqD8+8dUAEH+BVVh7HkjpCWC1CSw==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "co": "^4.6.0", + "expect": "^22.4.0", + "graceful-fs": "^4.1.11", + "is-generator-fn": "^1.0.0", + "jest-diff": "^22.4.0", + "jest-matcher-utils": "^22.4.0", + "jest-message-util": "^22.4.0", + "jest-snapshot": "^22.4.0", + "jest-util": "^22.4.1", + "source-map-support": "^0.5.0" + } + }, + "jest-matcher-utils": { + "version": "22.4.3", + "resolved": "http://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-22.4.3.tgz", + "integrity": "sha512-lsEHVaTnKzdAPR5t4B6OcxXo9Vy4K+kRRbG5gtddY8lBEC+Mlpvm1CJcsMESRjzUhzkz568exMV1hTB76nAKbA==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "jest-get-type": "^22.4.3", + "pretty-format": "^22.4.3" + } + }, + "jest-message-util": { + "version": "22.4.3", + "resolved": "http://registry.npmjs.org/jest-message-util/-/jest-message-util-22.4.3.tgz", + "integrity": "sha512-iAMeKxhB3Se5xkSjU0NndLLCHtP4n+GtCqV0bISKA5dmOXQfEbdEmYiu2qpnWBDCQdEafNDDU6Q+l6oBMd/+BA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0-beta.35", + "chalk": "^2.0.1", + "micromatch": "^2.3.11", + "slash": "^1.0.0", + "stack-utils": "^1.0.1" + } + }, + "jest-mock": { + "version": "22.4.3", + "resolved": "http://registry.npmjs.org/jest-mock/-/jest-mock-22.4.3.tgz", + "integrity": "sha512-+4R6mH5M1G4NK16CKg9N1DtCaFmuxhcIqF4lQK/Q1CIotqMs/XBemfpDPeVZBFow6iyUNu6EBT9ugdNOTT5o5Q==", + "dev": true + }, + "jest-regex-util": { + "version": "22.4.3", + "resolved": "http://registry.npmjs.org/jest-regex-util/-/jest-regex-util-22.4.3.tgz", + "integrity": "sha512-LFg1gWr3QinIjb8j833bq7jtQopiwdAs67OGfkPrvy7uNUbVMfTXXcOKXJaeY5GgjobELkKvKENqq1xrUectWg==", + "dev": true + }, + "jest-resolve": { + "version": "22.4.3", + "resolved": "http://registry.npmjs.org/jest-resolve/-/jest-resolve-22.4.3.tgz", + "integrity": "sha512-u3BkD/MQBmwrOJDzDIaxpyqTxYH+XqAXzVJP51gt29H8jpj3QgKof5GGO2uPGKGeA1yTMlpbMs1gIQ6U4vcRhw==", + "dev": true, + "requires": { + "browser-resolve": "^1.11.2", + "chalk": "^2.0.1" + } + }, + "jest-snapshot": { + "version": "22.4.3", + "resolved": "http://registry.npmjs.org/jest-snapshot/-/jest-snapshot-22.4.3.tgz", + "integrity": "sha512-JXA0gVs5YL0HtLDCGa9YxcmmV2LZbwJ+0MfyXBBc5qpgkEYITQFJP7XNhcHFbUvRiniRpRbGVfJrOoYhhGE0RQ==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "jest-diff": "^22.4.3", + "jest-matcher-utils": "^22.4.3", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "pretty-format": "^22.4.3" + } + }, + "jest-util": { + "version": "22.4.3", + "resolved": "http://registry.npmjs.org/jest-util/-/jest-util-22.4.3.tgz", + "integrity": "sha512-rfDfG8wyC5pDPNdcnAlZgwKnzHvZDu8Td2NJI/jAGKEGxJPYiE4F0ss/gSAkG4778Y23Hvbz+0GMrDJTeo7RjQ==", + "dev": true, + "requires": { + "callsites": "^2.0.0", + "chalk": "^2.0.1", + "graceful-fs": "^4.1.11", + "is-ci": "^1.0.10", + "jest-message-util": "^22.4.3", + "mkdirp": "^0.5.1", + "source-map": "^0.6.0" + } + }, + "jest-validate": { + "version": "22.4.4", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-22.4.4.tgz", + "integrity": "sha512-dmlf4CIZRGvkaVg3fa0uetepcua44DHtktHm6rcoNVtYlpwe6fEJRkMFsaUVcFHLzbuBJ2cPw9Gl9TKfnzMVwg==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "jest-config": "^22.4.4", + "jest-get-type": "^22.1.0", + "leven": "^2.1.0", + "pretty-format": "^22.4.0" + } + }, + "jsdom": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", + "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "acorn": "^5.5.3", + "acorn-globals": "^4.1.0", + "array-equal": "^1.0.0", + "cssom": ">= 0.3.2 < 0.4.0", + "cssstyle": "^1.0.0", + "data-urls": "^1.0.0", + "domexception": "^1.0.1", + "escodegen": "^1.9.1", + "html-encoding-sniffer": "^1.0.2", + "left-pad": "^1.3.0", + "nwsapi": "^2.0.7", + "parse5": "4.0.0", + "pn": "^1.1.0", + "request": "^2.87.0", + "request-promise-native": "^1.0.5", + "sax": "^1.2.4", + "symbol-tree": "^3.2.2", + "tough-cookie": "^2.3.4", + "w3c-hr-time": "^1.0.1", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.3", + "whatwg-mimetype": "^2.1.0", + "whatwg-url": "^6.4.1", + "ws": "^5.2.0", + "xml-name-validator": "^3.0.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "dev": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + }, + "pretty-format": { + "version": "22.4.3", + "resolved": "http://registry.npmjs.org/pretty-format/-/pretty-format-22.4.3.tgz", + "integrity": "sha512-S4oT9/sT6MN7/3COoOy+ZJeA92VmOnveLHgrwBE3Z1W5N9S2A1QGNYiE1z75DAENbJrXXUb+OWXhpJcg05QKQQ==", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0", + "ansi-styles": "^3.2.0" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "source-map-support": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", + "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "whatwg-url": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "yargs": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-10.1.2.tgz", + "integrity": "sha512-ivSoxqBGYOqQVruxD35+EyCFDYNEFL/Uo6FcOnz+9xZdZzK0Zzw4r4KhbrME1Oo2gOggwJod2MnsdamSG7H9ig==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^8.1.0" + } + }, + "yargs-parser": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-8.1.0.tgz", + "integrity": "sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "ts-loader": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-2.3.7.tgz", + "integrity": "sha512-8t3bu2FcEkXb+D4L+Cn8qiK2E2C6Ms4/GQChvz6IMbVurcFHLXrhW4EMtfaol1a1ASQACZGDUGit4NHnX9g7hQ==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "enhanced-resolve": "^3.0.0", + "loader-utils": "^1.0.2", + "semver": "^5.0.1" + }, + "dependencies": { + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "tsconfig-paths": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.6.0.tgz", + "integrity": "sha512-mrqQIP2F4e03aMTCiPdedCIT300//+q0ET53o5WqqtQjmEICxP9yfz/sHTpPqXpssuJEzODsEzJaLRaf5J2X1g==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "deepmerge": "^2.0.1", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, + "tsconfig-paths-webpack-plugin": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-2.0.0.tgz", + "integrity": "sha512-reAnVEGP7mNwOcXXYxQpsH7uY8blNJM/xgN2KYttVX+qkwfqA+nhRPpA7Fnomnlhm5Jz0EoSVwk4rtQu8hC54g==", + "dev": true, + "requires": { + "chalk": "^2.3.0", + "tsconfig-paths": "^3.1.1" + }, + "dependencies": { + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, + "tslint": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.11.0.tgz", + "integrity": "sha1-mPMMAurjzecAYgHkwzywi0hYHu0=", + "dev": true, + "requires": { + "babel-code-frame": "^6.22.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.7.0", + "minimatch": "^3.0.4", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.27.2" + }, + "dependencies": { + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "tslint-config-prettier": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/tslint-config-prettier/-/tslint-config-prettier-1.15.0.tgz", + "integrity": "sha512-06CgrHJxJmNYVgsmeMoa1KXzQRoOdvfkqnJth6XUkNeOz707qxN0WfxfhYwhL5kXHHbYJRby2bqAPKwThlZPhw==", + "dev": true + }, + "tslint-react": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tslint-react/-/tslint-react-3.6.0.tgz", + "integrity": "sha512-AIv1QcsSnj7e9pFir6cJ6vIncTqxfqeFF3Lzh8SuuBljueYzEAtByuB6zMaD27BL0xhMEqsZ9s5eHuCONydjBw==", + "dev": true, + "requires": { + "tsutils": "^2.13.1" + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "typescript": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.3.tgz", + "integrity": "sha512-+81MUSyX+BaSo+u2RbozuQk/UWx6hfG0a5gHu4ANEM4sU96XbuIyAB+rWBW1u70c6a5QuZfuYICn3s2UjuHUpA==", + "dev": true + }, + "uglify-js": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", + "dev": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1" + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, + "optional": true + }, + "uglifyjs-webpack-plugin": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.5.tgz", + "integrity": "sha512-hIQJ1yxAPhEA2yW/i7Fr+SXZVMp+VEI3d42RTHBgQd2yhp/1UdBcR3QEWPV5ahBxlqQDMEMTuTEvDHSFINfwSw==", + "dev": true, + "requires": { + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "schema-utils": "^0.4.5", + "serialize-javascript": "^1.4.0", + "source-map": "^0.6.1", + "uglify-es": "^3.3.4", + "webpack-sources": "^1.1.0", + "worker-farm": "^1.5.2" + }, + "dependencies": { + "ajv": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz", + "integrity": "sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "commander": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", + "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "schema-utils": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", + "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + } + }, + "uglify-es": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", + "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", + "dev": true, + "requires": { + "commander": "~2.13.0", + "source-map": "~0.6.1" + } + } + } + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", + "dev": true + }, + "uniqs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", + "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", + "dev": true + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.1.tgz", + "integrity": "sha512-n9cU6+gITaVu7VGj1Z8feKMmfAjEAQGhwD9fE3zvpRRa0wEIx8ODYkVGfSc94M2OX00tUFV8wH3zYbm1I8mxFg==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "dev": true, + "requires": { + "crypto-random-string": "^1.0.0" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "unzip-response": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", + "dev": true + }, + "upath": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", + "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", + "dev": true + }, + "update-notifier": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", + "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", + "dev": true, + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + } + } + }, + "urijs": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.1.tgz", + "integrity": "sha512-xVrGVi94ueCJNrBSTjWqjvtgvl3cyOTThp2zaMaFNGp3F542TR6sM3f2o8RqZl+AwteClSVmoCyt0ka4RjQOQg==", + "dev": true + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "url-loader": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-0.6.2.tgz", + "integrity": "sha512-h3qf9TNn53BpuXTTcpC+UehiRrl0Cv45Yr/xWayApjw6G8Bg2dGke7rIwDQ39piciWCWrC+WiqLjOh3SUp9n0Q==", + "dev": true, + "requires": { + "loader-utils": "^1.0.2", + "mime": "^1.4.1", + "schema-utils": "^0.3.0" + } + }, + "url-parse": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.3.tgz", + "integrity": "sha512-rh+KuAW36YKo0vClhQzLLveoj8FwPJNu65xLb7Mrt+eZht0IPT0IXgSv8gcMegZ6NvjJUALf6Mf25POlMwD1Fw==", + "dev": true, + "requires": { + "querystringify": "^2.0.0", + "requires-port": "^1.0.0" + } + }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "dev": true, + "requires": { + "prepend-http": "^1.0.1" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "vendors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.2.tgz", + "integrity": "sha512-w/hry/368nO21AN9QljsaIhb9ZiZtZARoVH5f3CsFbawdLdayCgKRPup7CggujvySMxx0I91NOyxdVENohprLQ==", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vm-browserify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dev": true, + "requires": { + "indexof": "0.0.1" + } + }, + "w3c-hr-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", + "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", + "dev": true, + "requires": { + "browser-process-hrtime": "^0.1.2" + } + }, + "walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "dev": true, + "requires": { + "makeerror": "1.0.x" + } + }, + "watch": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/watch/-/watch-0.10.0.tgz", + "integrity": "sha1-d3mLLaD5kQ1ZXxrOWwwiWFIfIdw=", + "dev": true + }, + "watchpack": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", + "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", + "dev": true, + "requires": { + "chokidar": "^2.0.2", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + } + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "webpack": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.8.1.tgz", + "integrity": "sha512-5ZXLWWsMqHKFr5y0N3Eo5IIisxeEeRAajNq4mELb/WELOR7srdbQk2N5XiyNy2A/AgvlR3AmeBCZJW8lHrolbw==", + "dev": true, + "requires": { + "acorn": "^5.0.0", + "acorn-dynamic-import": "^2.0.0", + "ajv": "^5.1.5", + "ajv-keywords": "^2.0.0", + "async": "^2.1.2", + "enhanced-resolve": "^3.4.0", + "escope": "^3.6.0", + "interpret": "^1.0.0", + "json-loader": "^0.5.4", + "json5": "^0.5.1", + "loader-runner": "^2.3.0", + "loader-utils": "^1.1.0", + "memory-fs": "~0.4.1", + "mkdirp": "~0.5.0", + "node-libs-browser": "^2.0.0", + "source-map": "^0.5.3", + "supports-color": "^4.2.1", + "tapable": "^0.2.7", + "uglifyjs-webpack-plugin": "^0.4.6", + "watchpack": "^1.4.0", + "webpack-sources": "^1.0.1", + "yargs": "^8.0.2" + }, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "ajv-keywords": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "dev": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "^2.0.0" + } + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + }, + "dependencies": { + "yargs": { + "version": "3.10.0", + "resolved": "http://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + } + } + }, + "uglifyjs-webpack-plugin": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz", + "integrity": "sha1-uVH0q7a9YX5m9j64kUmOORdj4wk=", + "dev": true, + "requires": { + "source-map": "^0.5.6", + "uglify-js": "^2.8.29", + "webpack-sources": "^1.0.1" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true + }, + "yargs": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz", + "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "read-pkg-up": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^7.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + } + } + }, + "yargs-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", + "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + } + } + } + } + }, + "webpack-dev-middleware": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz", + "integrity": "sha512-FCrqPy1yy/sN6U/SaEZcHKRXGlqU0DUaEBL45jkUYoB8foVb6wCnbIJ1HKIx+qUFTW+3JpVcCJCxZ8VATL4e+A==", + "dev": true, + "requires": { + "memory-fs": "~0.4.1", + "mime": "^1.5.0", + "path-is-absolute": "^1.0.0", + "range-parser": "^1.0.3", + "time-stamp": "^2.0.0" + } + }, + "webpack-dev-server": { + "version": "2.11.3", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-2.11.3.tgz", + "integrity": "sha512-Qz22YEFhWx+M2vvJ+rQppRv39JA0h5NNbOOdODApdX6iZ52Diz7vTPXjF7kJlfn+Uc24Qr48I3SZ9yncQwRycg==", + "dev": true, + "requires": { + "ansi-html": "0.0.7", + "array-includes": "^3.0.3", + "bonjour": "^3.5.0", + "chokidar": "^2.0.0", + "compression": "^1.5.2", + "connect-history-api-fallback": "^1.3.0", + "debug": "^3.1.0", + "del": "^3.0.0", + "express": "^4.16.2", + "html-entities": "^1.2.0", + "http-proxy-middleware": "~0.17.4", + "import-local": "^1.0.0", + "internal-ip": "1.2.0", + "ip": "^1.1.5", + "killable": "^1.0.0", + "loglevel": "^1.4.1", + "opn": "^5.1.0", + "portfinder": "^1.0.9", + "selfsigned": "^1.9.1", + "serve-index": "^1.7.2", + "sockjs": "0.3.19", + "sockjs-client": "1.1.5", + "spdy": "^3.4.1", + "strip-ansi": "^3.0.0", + "supports-color": "^5.1.0", + "webpack-dev-middleware": "1.12.2", + "yargs": "6.6.0" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "del": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", + "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", + "dev": true, + "requires": { + "globby": "^6.1.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "p-map": "^1.1.1", + "pify": "^3.0.0", + "rimraf": "^2.2.8" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "yargs": { + "version": "6.6.0", + "resolved": "http://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", + "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^4.2.0" + } + }, + "yargs-parser": { + "version": "4.2.1", + "resolved": "http://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", + "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", + "dev": true, + "requires": { + "camelcase": "^3.0.0" + } + } + } + }, + "webpack-manifest-plugin": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-1.3.2.tgz", + "integrity": "sha512-MX60Bv2G83Zks9pi3oLOmRgnPAnwrlMn+lftMrWBm199VQjk46/xgzBi9lPfpZldw2+EI2S+OevuLIaDuxCWRw==", + "dev": true, + "requires": { + "fs-extra": "^0.30.0", + "lodash": ">=3.5 <5" + }, + "dependencies": { + "fs-extra": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", + "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" + } + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + } + } + }, + "webpack-sources": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", + "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "websocket-driver": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz", + "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", + "dev": true, + "requires": { + "http-parser-js": ">=0.4.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", + "dev": true + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-fetch": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz", + "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=", + "dev": true + }, + "whatwg-mimetype": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.2.0.tgz", + "integrity": "sha512-5YSO1nMd5D1hY3WzAQV3PzZL83W3YeyR1yW9PcH26Weh1t+Vzh9B6XkDh7aXm83HBZ4nSMvkjvN2H2ySWIvBgw==", + "dev": true + }, + "whatwg-url": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-4.8.0.tgz", + "integrity": "sha1-0pgaqRSMHgCkHFphMRZqtGg7vMA=", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", + "dev": true + } + } + }, + "whet.extend": { + "version": "0.9.9", + "resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz", + "integrity": "sha1-+HfVv2SMl+WqVC+twW1qJZucEaE=", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "widest-line": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", + "dev": true, + "requires": { + "string-width": "^2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + }, + "worker-farm": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.6.0.tgz", + "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==", + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write-file-atomic": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", + "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "ws": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", + "dev": true + }, + "xml-name-validator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz", + "integrity": "sha1-TYuPHszTQZqjYgYb7O9RXh5VljU=", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", + "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^5.0.0" + } + }, + "yargs-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", + "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "dev": true, + "requires": { + "camelcase": "^3.0.0" + } + } + } +} diff --git a/src/Tgstation.Server.Host/ClientApp/package.json b/src/Tgstation.Server.Host/ClientApp/package.json new file mode 100644 index 0000000000..b2af8d04b2 --- /dev/null +++ b/src/Tgstation.Server.Host/ClientApp/package.json @@ -0,0 +1,20 @@ +{ + "private": true, + "dependencies": { + "react-dom": "^16.5.2", + "tgstation-server-control-panel": "^0.1.5" + }, + "scripts": { + "build": "rimraf build && react-scripts-ts build && cp-cli node_modules/tgstation-server-control-panel/build/public/ build/ && cp-cli ../../../build/tgs.ico build/favicon.ico && minify-json build && rimraf ../wwwroot && move-cli build ../wwwroot" + }, + "devDependencies": { + "@types/node": "^10.12.0", + "@types/react-dom": "^16.0.9", + "cp-cli": "^1.1.2", + "minify-json": "^1.0.0", + "move-cli": "^1.2.0", + "react-scripts-ts": "^3.1.0", + "rimraf": "^2.6.2", + "typescript": "^3.1.3" + } +} diff --git a/src/Tgstation.Server.Host/ClientApp/public/index.html b/src/Tgstation.Server.Host/ClientApp/public/index.html new file mode 100644 index 0000000000..b74575714f --- /dev/null +++ b/src/Tgstation.Server.Host/ClientApp/public/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + + Tgstation Server Control Panel + + + +
+ + + diff --git a/src/Tgstation.Server.Host/ClientApp/public/manifest.json b/src/Tgstation.Server.Host/ClientApp/public/manifest.json new file mode 100644 index 0000000000..5276a8ba64 --- /dev/null +++ b/src/Tgstation.Server.Host/ClientApp/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "TGS", + "name": "Tgstation Server", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": "./", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/src/Tgstation.Server.Host/ClientApp/src/index.css b/src/Tgstation.Server.Host/ClientApp/src/index.css new file mode 100644 index 0000000000..b4cc7250b9 --- /dev/null +++ b/src/Tgstation.Server.Host/ClientApp/src/index.css @@ -0,0 +1,5 @@ +body { + margin: 0; + padding: 0; + font-family: sans-serif; +} diff --git a/src/Tgstation.Server.Host/ClientApp/src/index.tsx b/src/Tgstation.Server.Host/ClientApp/src/index.tsx new file mode 100644 index 0000000000..b59b0f0853 --- /dev/null +++ b/src/Tgstation.Server.Host/ClientApp/src/index.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; + +import ControlPanel from 'tgstation-server-control-panel'; + +import registerServiceWorker from './registerServiceWorker'; + +import './index.css'; + +const contentElement = document.getElementById('root') as HTMLElement; +const legacyUserLanguageKey = 'userLanguage'; +const userLang = navigator.language || navigator[legacyUserLanguageKey]; +const serverAddress = window.location.href; +const controlPanel = ; + +registerServiceWorker(); +ReactDOM.render(controlPanel, contentElement); diff --git a/src/Tgstation.Server.Host/ClientApp/src/registerServiceWorker.ts b/src/Tgstation.Server.Host/ClientApp/src/registerServiceWorker.ts new file mode 100644 index 0000000000..e10d7c6155 --- /dev/null +++ b/src/Tgstation.Server.Host/ClientApp/src/registerServiceWorker.ts @@ -0,0 +1,124 @@ +// tslint:disable:no-console +// tslint:disable:curly +// In production, we register a service worker to serve assets from local cache. + +// This lets the app load faster on subsequent visits in production, and gives +// it offline capabilities. However, it also means that developers (and users) +// will only see deployed updates on the 'N+1' visit to a page, since previously +// cached resources are updated in the background. + +// To learn more about the benefits of this model, read https://goo.gl/KwvDNy. +// This link also includes instructions on opting out of this behavior. + +const isLocalhost = Boolean( + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.1/8 is considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) +); + +export default function register() { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL( + process.env.PUBLIC_URL!, + window.location.toString() + ); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 + return; + } + + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + + if (isLocalhost) { + // This is running on localhost. Lets check if a service worker still exists or not. + checkValidServiceWorker(swUrl); + + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit https://goo.gl/SC7cgQ' + ); + }); + } else { + // Is not local host. Just register service worker + registerValidSW(swUrl); + } + }); + } +} + +function registerValidSW(swUrl: string) { + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + if (installingWorker) { + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the old content will have been purged and + // the fresh content will have been added to the cache. + // It's the perfect time to display a 'New content is + // available; please refresh.' message in your web app. + console.log('New content is available; please refresh.'); + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // 'Content is cached for offline use.' message. + console.log('Content is cached for offline use.'); + } + } + }; + } + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); +} + +function checkValidServiceWorker(swUrl: string) { + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl) + .then(response => { + // Ensure service worker exists, and that we really are getting a JS file. + if ( + response.status === 404 || + response.headers.get('content-type')!.indexOf('javascript') === -1 + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then(registration => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl); + } + }) + .catch(() => { + console.log( + 'No internet connection found. App is running in offline mode.' + ); + }); +} + +export function unregister() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready.then(registration => { + registration.unregister(); + }); + } +} diff --git a/src/Tgstation.Server.Host/ClientApp/tsconfig.json b/src/Tgstation.Server.Host/ClientApp/tsconfig.json new file mode 100644 index 0000000000..2a0d8cf454 --- /dev/null +++ b/src/Tgstation.Server.Host/ClientApp/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "outDir": "build/dist", + "module": "esnext", + "target": "es5", + "lib": ["es6", "dom"], + "sourceMap": true, + "allowJs": true, + "jsx": "react", + "moduleResolution": "node", + "rootDir": "src", + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": true, + "importHelpers": true, + "strictNullChecks": true, + "suppressImplicitAnyIndexErrors": true, + "noUnusedLocals": true + }, + "exclude": [ + "node_modules", + "build", + "scripts", + "acceptance-tests", + "webpack", + "jest", + "src/setupTests.ts" + ] +} diff --git a/src/Tgstation.Server.Host/ClientApp/tsconfig.prod.json b/src/Tgstation.Server.Host/ClientApp/tsconfig.prod.json new file mode 100644 index 0000000000..fc8520e737 --- /dev/null +++ b/src/Tgstation.Server.Host/ClientApp/tsconfig.prod.json @@ -0,0 +1,3 @@ +{ + "extends": "./tsconfig.json" +} diff --git a/src/Tgstation.Server.Host/ClientApp/tslint.json b/src/Tgstation.Server.Host/ClientApp/tslint.json new file mode 100644 index 0000000000..5ad33a8e73 --- /dev/null +++ b/src/Tgstation.Server.Host/ClientApp/tslint.json @@ -0,0 +1,14 @@ +{ + "extends": [ "tslint:recommended", "tslint-react", "tslint-config-prettier" ], + "linterOptions": { + "exclude": [ + "config/**/*.js", + "node_modules/**/*.ts", + "coverage/lcov-report/*.js" + ] + }, + "rules": { + "curly": [ true, "as-needed" ], + "semicolon": [ true, "always" ] + } +} diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs b/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs index a1a3535225..403b1bfa6a 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs @@ -27,8 +27,8 @@ namespace Tgstation.Server.Host.Components.Byond DreamMakerPath = dreamMakerPath ?? throw new ArgumentNullException(nameof(dreamMakerPath)); } - //at one point in design, byond versions were to delete themselves if they werent the active version - //That changed at some point so these functions are intentioanlly left blank + // at one point in design, byond versions were to delete themselves if they weren't the active version + // That changed at some point so these functions are intentioanlly left blank /// public void Dispose() { } diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs index 8c597e7d23..73c458f7f0 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs @@ -24,6 +24,7 @@ namespace Tgstation.Server.Host.Components.Byond /// The file in which we store the for installations /// const string VersionFileName = "Version.txt"; + /// /// The file in which we store the for the active installation /// @@ -119,6 +120,7 @@ namespace Tgstation.Server.Host.Components.Byond if (!installed) installedVersions.Add(versionKey, ourTcs.Task); } + if (installed) using (cancellationToken.Register(() => ourTcs.SetCanceled())) { @@ -126,7 +128,8 @@ namespace Tgstation.Server.Host.Components.Byond cancellationToken.ThrowIfCancellationRequested(); return; } - //okay up to us to install it then + + // okay up to us to install it then try { await eventConsumer.HandleEvent(EventType.ByondInstallStart, new List { versionKey }, cancellationToken).ConfigureAwait(false); @@ -141,12 +144,12 @@ namespace Tgstation.Server.Host.Components.Byond await ioManager.ZipToDirectory(versionKey, download, cancellationToken).ConfigureAwait(false); await byondInstaller.InstallByond(ioManager.ResolvePath(versionKey), version, cancellationToken).ConfigureAwait(false); - //make sure to do this last because this is what tells us we have a valid version in the future + // make sure to do this last because this is what tells us we have a valid version in the future await ioManager.WriteAllBytes(ioManager.ConcatPath(versionKey, VersionFileName), Encoding.UTF8.GetBytes(version.ToString()), cancellationToken).ConfigureAwait(false); } catch (WebException e) { - //since the user can easily provide non-exitent version numbers, we'll turn this into a JobException + // since the user can easily provide non-exitent version numbers, we'll turn this into a JobException throw new JobException(String.Format(CultureInfo.InvariantCulture, "Error downloading BYOND version: {0}", e.Message)); } catch (OperationCanceledException) @@ -205,9 +208,8 @@ namespace Tgstation.Server.Host.Components.Byond { async Task GetActiveVersion() { - if (!await ioManager.FileExists(ActiveVersionFileName, cancellationToken).ConfigureAwait(false)) - return null; - return await ioManager.ReadAllBytes(ActiveVersionFileName, cancellationToken).ConfigureAwait(false); + var activeVersionFileExists = await ioManager.FileExists(ActiveVersionFileName, cancellationToken).ConfigureAwait(false); + return !activeVersionFileExists ? null : await ioManager.ReadAllBytes(ActiveVersionFileName, cancellationToken).ConfigureAwait(false); } var activeVersionBytesTask = GetActiveVersion(); @@ -221,9 +223,10 @@ namespace Tgstation.Server.Host.Components.Byond if (!await ioManager.FileExists(versionFile, cancellationToken).ConfigureAwait(false)) { logger.LogInformation("Cleaning unparsable version path: {0}", ioManager.ResolvePath(path)); - await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(false); //cleanup + await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(false); // cleanup return; } + var bytes = await ioManager.ReadAllBytes(versionFile, cancellationToken).ConfigureAwait(false); var text = Encoding.UTF8.GetString(bytes); if (Version.TryParse(text, out var version)) @@ -236,8 +239,9 @@ namespace Tgstation.Server.Host.Components.Byond return; } } + await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(false); - }; + } await Task.WhenAll(directories.Select(x => ReadVersion(x))).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Components/Byond/IByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/IByondInstaller.cs index b0526707b9..6b562f056e 100644 --- a/src/Tgstation.Server.Host/Components/Byond/IByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/IByondInstaller.cs @@ -33,7 +33,7 @@ namespace Tgstation.Server.Host.Components.Byond /// The path to the BYOND installation /// The of BYOND being installed /// The for the operation - /// + /// A representing the running operation Task InstallByond(string path, Version version, CancellationToken cancellationToken); /// diff --git a/src/Tgstation.Server.Host/Components/Byond/IByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/IByondManager.cs index 767d15537e..7ace2266ac 100644 --- a/src/Tgstation.Server.Host/Components/Byond/IByondManager.cs +++ b/src/Tgstation.Server.Host/Components/Byond/IByondManager.cs @@ -26,6 +26,7 @@ namespace Tgstation.Server.Host.Components.Byond /// /// The new /// The for the operation + /// A representing the running operation Task ChangeVersion(Version version, CancellationToken cancellationToken); /// diff --git a/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs index 3c25ac9175..6d741bb952 100644 --- a/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs @@ -1,11 +1,9 @@ using Microsoft.Extensions.Logging; using System; using System.Globalization; -using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; -using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components.Byond @@ -18,7 +16,8 @@ namespace Tgstation.Server.Host.Components.Byond /// /// The URL format string for getting BYOND linux version {0}.{1} zipfile /// - const string ByondRevisionsURL = "https://secure.byond.com/download/build/{0}/{0}.{1}_byond_linux.zip"; + const string ByondRevisionsURLTemplate = "https://secure.byond.com/download/build/{0}/{0}.{1}_byond_linux.zip"; + /// /// Path to the BYOND cache /// @@ -85,7 +84,7 @@ namespace Tgstation.Server.Host.Components.Byond if (version == null) throw new ArgumentNullException(nameof(version)); - var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsURL, version.Major, version.Minor); + var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsURLTemplate, version.Major, version.Minor); return await ioManager.DownloadFile(new Uri(url), cancellationToken).ConfigureAwait(false); } @@ -98,8 +97,8 @@ namespace Tgstation.Server.Host.Components.Byond if (version == null) throw new ArgumentNullException(nameof(version)); - //write the scripts for running the ting - //need to add $ORIGIN to LD_LIBRARY_PATH + // write the scripts for running the ting + // need to add $ORIGIN to LD_LIBRARY_PATH const string StandardScript = "#!/bin/sh\nexport LD_LIBRARY_PATH=\"\\$ORIGIN:$LD_LIBRARY_PATH\"\nBASEDIR=$(dirname \"$0\")\nexec \"$BASEDIR/{0}\" \"$@\"\n"; var dreamDaemonScript = String.Format(CultureInfo.InvariantCulture, StandardScript, DreamDaemonExecutableName); diff --git a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs index c3f051fa60..e4b46e5384 100644 --- a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs @@ -17,19 +17,23 @@ namespace Tgstation.Server.Host.Components.Byond /// /// The URL format string for getting BYOND windows version {0}.{1} zipfile /// - const string ByondRevisionsURL = "https://secure.byond.com/download/build/{0}/{0}.{1}_byond.zip"; + const string ByondRevisionsURLTemplate = "https://secure.byond.com/download/build/{0}/{0}.{1}_byond.zip"; + /// /// Directory to byond installation configuration /// const string ByondConfigDir = "byond/cfg"; + /// /// BYOND's DreamDaemon config file /// const string ByondDDConfig = "daemon.txt"; + /// /// Setting to add to to suppress an invisible user prompt for running a trusted mode .dmb /// const string ByondNoPromptTrustedMode = "trusted-check 0"; + /// /// The directory that contains the BYOND directx redistributable /// @@ -105,7 +109,7 @@ namespace Tgstation.Server.Host.Components.Byond /// public Task DownloadVersion(Version version, CancellationToken cancellationToken) { - var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsURL, version.Major, version.Minor); + var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsURLTemplate, version.Major, version.Minor); return ioManager.DownloadFile(new Uri(url), cancellationToken); } @@ -118,20 +122,21 @@ namespace Tgstation.Server.Host.Components.Byond var configPath = ioManager.ConcatPath(path, ByondConfigDir); await ioManager.CreateDirectory(configPath, cancellationToken).ConfigureAwait(false); await ioManager.WriteAllBytes(ioManager.ConcatPath(configPath, ByondDDConfig), Encoding.UTF8.GetBytes(ByondNoPromptTrustedMode), cancellationToken).ConfigureAwait(false); - }; + } var setNoPromptTrustedModeTask = SetNoPromptTrusted(); - //after this version lummox made DD depend of directx lol - //but then he became amazing and not only fixed it but also gave us 30s compiles \[T]/ + // after this version lummox made DD depend of directx lol + // but then he became amazing and not only fixed it but also gave us 30s compiles \[T]/ if (version.Major == 512 && version.Minor >= 1427 && version.Minor < 1452 && !installedDirectX) using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) - //check again because race conditions if (!installedDirectX) { - //always install it, it's pretty fast and will do better redundancy checking than us + // ^check again because race conditions + // always install it, it's pretty fast and will do better redundancy checking than us var rbdx = ioManager.ConcatPath(path, ByondDXDir); - //noShellExecute because we aren't doing runas shennanigans + + // noShellExecute because we aren't doing runas shennanigans IProcess directXInstaller; try { @@ -141,6 +146,7 @@ namespace Tgstation.Server.Host.Components.Byond { throw new JobException("Unable to start DirectX installer process! Is the server running with admin privileges?", e); } + using (directXInstaller) { int exitCode; diff --git a/src/Tgstation.Server.Host/Components/Chat/Channel.cs b/src/Tgstation.Server.Host/Components/Chat/Channel.cs index 131ce67962..fe5d6acb15 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Channel.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Channel.cs @@ -38,12 +38,12 @@ namespace Tgstation.Server.Host.Components.Chat /// /// If this is considered a channel for admin commands /// - public bool IsAdmin { get; set; } + public bool IsAdminChannel { get; set; } /// /// If this is a 1-to-1 chat channel /// - public bool IsPrivate { get; set; } + public bool IsPrivateChannel { get; set; } /// /// For user use diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index b4d68b1139..67c028623d 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -15,6 +15,8 @@ using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components.Chat { /// + // TODO: Decomplexify + #pragma warning disable CA1506 sealed class Chat : IChat, IRestartHandler { const string CommonMention = "!tgs"; @@ -177,6 +179,7 @@ namespace Tgstation.Server.Host.Components.Chat else task = Task.CompletedTask; } + await task.ConfigureAwait(false); return provider; } @@ -190,12 +193,12 @@ namespace Tgstation.Server.Host.Components.Chat /// A representing the running operation async Task ProcessMessage(IProvider provider, Message message, CancellationToken cancellationToken) { - //map the channel if it's private and we haven't seen it + // map the channel if it's private and we haven't seen it lock (providers) { var providerId = providers.Where(x => x.Value == provider).Select(x => x.Key).First(); var enumerable = mappedChannels.Where(x => x.Value.ProviderId == providerId && x.Value.ProviderChannelId == message.User.Channel.RealId); - if (message.User.Channel.IsPrivate) + if (message.User.Channel.IsPrivateChannel) lock (mappedChannels) { if (!provider.Connected) @@ -219,11 +222,11 @@ namespace Tgstation.Server.Host.Components.Chat } else { - //need to add tag and isAdminChannel + // need to add tag and isAdminChannel var mapping = enumerable.First().Value; message.User.Channel.Id = mapping.Channel.Id; message.User.Channel.Tag = mapping.Channel.Tag; - message.User.Channel.IsAdmin = mapping.Channel.IsAdmin; + message.User.Channel.IsAdminChannel = mapping.Channel.IsAdminChannel; } } @@ -236,8 +239,8 @@ namespace Tgstation.Server.Host.Components.Chat var addressed = address == CommonMention.ToUpperInvariant() || address == provider.BotMention.ToUpperInvariant(); - if (!addressed && !message.User.Channel.IsPrivate) - //no mention + // no mention + if (!addressed && !message.User.Channel.IsPrivateChannel) return; logger.LogTrace("Chat command: {0}. User (True provider Id): {1}", message.Content, JsonConvert.SerializeObject(message.User)); @@ -247,7 +250,7 @@ namespace Tgstation.Server.Host.Components.Chat if (splits.Count == 0) { - //just a mention + // just a mention await SendMessage("Hi!", new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); return; } @@ -266,8 +269,9 @@ namespace Tgstation.Server.Host.Components.Chat await Task.WhenAll(tasks).ConfigureAwait(false); handler = tasks.SelectMany(x => x.Result).Where(x => x.Name.ToUpperInvariant() == commandName).FirstOrDefault(); } + return handler; - }; + } const string UnknownCommandMessage = "Unknown command! Type '?' or 'help' for available commands."; @@ -290,6 +294,7 @@ namespace Tgstation.Server.Host.Components.Chat else helpText = UnknownCommandMessage; } + await SendMessage(helpText, new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); return; } @@ -302,7 +307,7 @@ namespace Tgstation.Server.Host.Components.Chat return; } - if (commandHandler.AdminOnly && !message.User.Channel.IsAdmin) + if (commandHandler.AdminOnly && !message.User.Channel.IsAdminChannel) { await SendMessage("Use this command in an admin channel!", new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); return; @@ -318,7 +323,7 @@ namespace Tgstation.Server.Host.Components.Chat } catch (Exception e) { - //error bc custom commands should reply about why it failed + // error bc custom commands should reply about why it failed logger.LogError("Error processing chat command: {0}", e); await SendMessage("Internal error processing command!", new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); } @@ -336,11 +341,11 @@ namespace Tgstation.Server.Host.Components.Chat { while (!cancellationToken.IsCancellationRequested) { - //prune disconnected providers + // prune disconnected providers foreach (var I in messageTasks.Where(x => !x.Key.Connected).ToList()) messageTasks.Remove(I.Key); - //add new ones + // add new ones Task updatedTask; lock (this) updatedTask = connectionsUpdated.Task; @@ -355,10 +360,10 @@ namespace Tgstation.Server.Host.Components.Chat continue; } - //wait for a message + // wait for a message await Task.WhenAny(updatedTask, Task.WhenAny(messageTasks.Select(x => x.Value))).ConfigureAwait(false); - //process completed ones + // process completed ones foreach (var I in messageTasks.Where(x => x.Value.IsCompleted).ToList()) { var message = await I.Value.ConfigureAwait(false); @@ -385,7 +390,7 @@ namespace Tgstation.Server.Host.Components.Chat if (provider == null) return; var results = await provider.MapChannels(newChannels, cancellationToken).ConfigureAwait(false); - if (results == null) //aborted + if (results == null) // aborted return; var mappings = Enumerable.Zip(newChannels, results, (x, y) => new ChannelMapping { @@ -407,7 +412,7 @@ namespace Tgstation.Server.Host.Components.Chat lock (mappedChannels) { lock (providers) - if (!providers.TryGetValue(connectionId, out IProvider verify) || verify != provider) //aborted again + if (!providers.TryGetValue(connectionId, out IProvider verify) || verify != provider) // aborted again return; foreach (var I in mappings) { @@ -419,6 +424,7 @@ namespace Tgstation.Server.Host.Components.Chat lock (trackingContexts) task = Task.WhenAll(trackingContexts.Select(x => x.SetChannels(mappedChannels.Select(y => y.Value.Channel), cancellationToken))); } + await task.ConfigureAwait(false); } @@ -444,7 +450,7 @@ namespace Tgstation.Server.Host.Components.Chat Task disconnectTask; lock (providers) { - //raw settings changes forces a rebuild of the provider + // raw settings changes forces a rebuild of the provider if (providers.TryGetValue(newSettings.Id, out provider)) { providers.Remove(newSettings.Id); @@ -471,7 +477,7 @@ namespace Tgstation.Server.Host.Components.Chat await provider.Connect(cancellationToken).ConfigureAwait(false); lock (this) { - //same thread shennanigans + // same thread shennanigans var oldOne = connectionsUpdated; connectionsUpdated = new TaskCompletionSource(); oldOne.SetResult(null); @@ -506,7 +512,7 @@ namespace Tgstation.Server.Host.Components.Chat { List wdChannels; message = String.Format(CultureInfo.InvariantCulture, "WD: {0}", message); - lock (mappedChannels) //so it doesn't change while we're using it + lock (mappedChannels) // so it doesn't change while we're using it wdChannels = mappedChannels.Where(x => x.Value.IsWatchdogChannel).Select(x => x.Key).ToList(); return SendMessage(message, wdChannels, cancellationToken); } @@ -516,7 +522,7 @@ namespace Tgstation.Server.Host.Components.Chat { List wdChannels; message = String.Format(CultureInfo.InvariantCulture, "DM: {0}", message); - lock (mappedChannels) //so it doesn't change while we're using it + lock (mappedChannels) // so it doesn't change while we're using it wdChannels = mappedChannels.Where(x => x.Value.IsUpdatesChannel).Select(x => x.Key).ToList(); return SendMessage(message, wdChannels, cancellationToken); } @@ -547,11 +553,17 @@ namespace Tgstation.Server.Host.Components.Chat if (customCommandHandler == null) throw new InvalidOperationException("RegisterCommandHandler() hasn't been called!"); JsonTrackingContext context = null; - context = new JsonTrackingContext(ioManager, customCommandHandler, loggerFactory.CreateLogger(), () => - { - lock (trackingContexts) - trackingContexts.Remove(context); - }, ioManager.ConcatPath(basePath, commandsJsonName), ioManager.ConcatPath(basePath, channelsJsonName)); + context = new JsonTrackingContext( + ioManager, + customCommandHandler, + loggerFactory.CreateLogger(), + () => + { + lock (trackingContexts) + trackingContexts.Remove(context); + }, + ioManager.ConcatPath(basePath, commandsJsonName), + ioManager.ConcatPath(basePath, channelsJsonName)); Task task; lock (trackingContexts) { @@ -559,6 +571,7 @@ namespace Tgstation.Server.Host.Components.Chat lock (mappedChannels) task = context.SetChannels(mappedChannels.Select(y => y.Value.Channel), cancellationToken); } + await task.ConfigureAwait(false); return context; } @@ -598,7 +611,7 @@ namespace Tgstation.Server.Host.Components.Chat { var message = updateVersion == null ? "TGS: Restart requested..." : String.Format(CultureInfo.InvariantCulture, "TGS: Updating to version {0}...", updateVersion); List wdChannels; - lock (mappedChannels) //so it doesn't change while we're using it + lock (mappedChannels) // so it doesn't change while we're using it wdChannels = mappedChannels.Select(x => x.Key).ToList(); return SendMessage(message, wdChannels, cancellationToken); } diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/ICommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/ICommand.cs index 8694606731..4ff8799ce7 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/ICommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/ICommand.cs @@ -19,7 +19,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands string HelpText { get; } /// - /// If the command should only be available to s who's has set + /// If the command should only be available to s who's has set /// bool AdminOnly { get; } diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs index 90db91a969..8e2b9f9c19 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs @@ -61,6 +61,8 @@ namespace Tgstation.Server.Host.Components.Chat.Commands } /// + // TODO: Decomplexify + #pragma warning disable CA1506 public async Task Invoke(string arguments, User user, CancellationToken cancellationToken) { IEnumerable results = null; @@ -73,6 +75,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands return "Repository unavailable!"; head = repo.Head; } + await databaseContextFactory.UseContext(async db => results = await db.RevisionInformations.Where(x => x.Instance.Id == instance.Id && x.CommitSha == head) .SelectMany(x => x.ActiveTestMerges) .Select(x => x.TestMerge) @@ -89,10 +92,8 @@ namespace Tgstation.Server.Host.Components.Chat.Commands results = watchdog.ActiveCompileJob?.RevisionInformation.ActiveTestMerges.Select(x => x.TestMerge).ToList() ?? new List(); } - if (!results.Any()) - return "None!"; - - return String.Join(", ", results.Select(x => String.Format(CultureInfo.InvariantCulture, "#{0} at {1}", x.Number, x.PullRequestRevision.Substring(0, 7)))); + return !results.Any() ? "None!" : String.Join(", ", results.Select(x => String.Format(CultureInfo.InvariantCulture, "#{0} at {1}", x.Number, x.PullRequestRevision.Substring(0, 7)))); } + #pragma warning restore CA1506 } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs index 13b5a68f74..858d4bc8c0 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs @@ -27,7 +27,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands /// /// Construct a /// - /// + /// The value of public VersionCommand(IApplication application) { this.application = application ?? throw new ArgumentNullException(nameof(application)); diff --git a/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs b/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs index 1aee8b6ea7..41646c7813 100644 --- a/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs +++ b/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs @@ -38,6 +38,15 @@ namespace Tgstation.Server.Host.Components.Chat bool active; + /// + /// Construct a + /// + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of public JsonTrackingContext(IIOManager ioManager, ICustomCommandHandler customCommandHandler, ILogger logger, Action onDispose, string commandsPath, string channelsPath) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); @@ -90,6 +99,7 @@ namespace Tgstation.Server.Host.Components.Chat { logger.LogWarning("Error retrieving custom commands! Exception: {0}", e); } + return new List(); } diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs index 711454887c..fcf6137f0e 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs @@ -54,7 +54,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// /// The mention provided by the Discord library /// The normalized mention - static string NormalizeMention(string fromDiscord) => fromDiscord.Replace("!", "", StringComparison.Ordinal); + static string NormalizeMention(string fromDiscord) => fromDiscord.Replace("!", String.Empty, StringComparison.Ordinal); /// /// Construct a @@ -97,10 +97,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers Channel = new Channel { RealId = e.Channel.Id, - IsPrivate = pm, + IsPrivateChannel = pm, ConnectionName = pm ? e.Author.Username : (e.Channel as ITextChannel)?.Guild.Name ?? "UNKNOWN", FriendlyName = e.Channel.Name - //isAdmin and Tag populated by manager + + // isAdmin and Tag populated by manager }, FriendlyName = e.Author.Username, Mention = NormalizeMention(e.Author.Mention) @@ -182,19 +183,19 @@ namespace Tgstation.Server.Host.Components.Chat.Providers if (!channel.DiscordChannelId.HasValue) throw new InvalidOperationException("ChatChannel missing DiscordChannelId!"); - if (!(client.GetChannel(channel.DiscordChannelId.Value) is ITextChannel discordChannel)) - return null; + if (client.GetChannel(channel.DiscordChannelId.Value) is ITextChannel discordChannel) + return new Channel + { + RealId = discordChannel.Id, + IsAdminChannel = channel.IsAdminChannel == true, + ConnectionName = discordChannel.Guild.Name, + FriendlyName = discordChannel.Name, + IsPrivateChannel = false, + Tag = channel.Tag + }; - return new Channel - { - RealId = discordChannel.Id, - IsAdmin = channel.IsAdminChannel == true, - ConnectionName = discordChannel.Guild.Name, - FriendlyName = discordChannel.Name, - IsPrivate = false, - Tag = channel.Tag - }; - }; + return null; + } var enumerator = channels.Select(x => GetChannelForChatChannel(x)).Where(x => x != null).ToList(); diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index cd08c1760e..cce4b47d37 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -44,18 +44,22 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// Address of the server to connect to /// readonly string address; + /// /// Port of the server to connect to /// readonly ushort port; + /// /// IRC nickname /// readonly string nickname; + /// /// Password which will used for authentication /// readonly string password; + /// /// The of /// @@ -134,7 +138,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers UseSsl = useSsl }; if (useSsl) - client.ValidateServerCertificate = true; //dunno if it defaults to that or what + client.ValidateServerCertificate = true; // dunno if it defaults to that or what client.OnChannelMessage += Client_OnChannelMessage; client.OnQueryMessage += Client_OnQueryMessage; @@ -151,7 +155,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers if (Connected) { disconnecting = true; - client.Disconnect(); //just closes the socket + client.Disconnect(); // just closes the socket } } @@ -184,8 +188,9 @@ namespace Tgstation.Server.Host.Components.Chat.Providers if (dicToCheck == queryChannelIdMap) channelIdMap.Add(resultId.Value, null); } + return resultId.Value; - }; + } ulong userId, channelId; lock (this) @@ -204,8 +209,9 @@ namespace Tgstation.Server.Host.Components.Chat.Providers ConnectionName = address, FriendlyName = isPrivate ? String.Format(CultureInfo.InvariantCulture, "PM: {0}", channelName) : channelName, RealId = channelId, - IsPrivate = isPrivate - //isAdmin and Tag populated by manager + IsPrivateChannel = isPrivate + + // isAdmin and Tag populated by manager }, FriendlyName = username, RealId = userId, @@ -247,9 +253,10 @@ namespace Tgstation.Server.Host.Components.Chat.Providers { if (passwordType == IrcPasswordType.Sasl) { - client.WriteLine("CAP REQ :sasl", Priority.Critical); //needs to be put in the buffer before anything else + client.WriteLine("CAP REQ :sasl", Priority.Critical); // needs to be put in the buffer before anything else cancellationToken.ThrowIfCancellationRequested(); } + client.Login(nickname, nickname, 0, nickname); } @@ -260,7 +267,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } else if (passwordType == IrcPasswordType.Sasl) { - //wait for the sasl ack or timeout + // wait for the sasl ack or timeout var recievedAck = false; var recievedPlus = false; client.OnReadLine += (sender, e) => @@ -285,7 +292,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers for (; !recievedPlus && DateTimeOffset.Now <= endTime; asyncDelayer.Delay(listenTimeSpan, cancellationToken).GetAwaiter().GetResult()) client.Listen(false); - //Stolen! https://github.com/znc/znc/blob/1e697580155d5a38f8b5a377f3b1d94aaa979539/modules/sasl.cpp#L196 + // Stolen! https://github.com/znc/znc/blob/1e697580155d5a38f8b5a377f3b1d94aaa979539/modules/sasl.cpp#L196 var authString = String.Format(CultureInfo.InvariantCulture, "{0}{1}{0}{1}{2}", nickname, '\0', password); var b64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(authString)); var authLine = String.Format(CultureInfo.InvariantCulture, "AUTHENTICATE {0}", b64); @@ -306,10 +313,12 @@ namespace Tgstation.Server.Host.Components.Chat.Providers if (disconnecting || !client.IsConnected) break; client.Listen(false); - //ensure we have the correct nick + + // ensure we have the correct nick if (client.GetIrcUser(nickname) == null) client.RfcNick(nickname); } + client.Listen(); }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); } @@ -322,6 +331,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers logger.LogWarning("Unable to connect to IRC: {0}", e); return false; } + return true; }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); @@ -336,7 +346,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers { try { - client.RfcQuit("Mr. Stark, I don't feel so good...", Priority.Critical); //priocritical otherwise it wont go through + client.RfcQuit("Mr. Stark, I don't feel so good...", Priority.Critical); // priocritical otherwise it wont go through } catch (Exception e) { @@ -363,7 +373,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers throw new InvalidOperationException("ChatChannel missing IrcChannel!"); lock (this) { - var hs = new HashSet(); //for unique inserts + var hs = new HashSet(); // for unique inserts foreach (var I in channels) hs.Add(I.IrcChannel); var toPart = new List(); @@ -390,13 +400,14 @@ namespace Tgstation.Server.Host.Components.Chat.Providers id = channelIdCounter++; channelIdMap.Add(id.Value, x.IrcChannel); } + return new Channel { RealId = id.Value, - IsAdmin = x.IsAdminChannel == true, + IsAdminChannel = x.IsAdminChannel == true, ConnectionName = address, FriendlyName = channelIdMap[id.Value], - IsPrivate = false, + IsPrivateChannel = false, Tag = x.Tag }; }).ToList(); diff --git a/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs index c22217328c..c077cab399 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs @@ -33,10 +33,12 @@ namespace Tgstation.Server.Host.Components.Compiler /// The for the /// readonly IDatabaseContextFactory databaseContextFactory; + /// /// The for the /// readonly IIOManager ioManager; + /// /// The for the /// @@ -46,6 +48,7 @@ namespace Tgstation.Server.Host.Components.Compiler /// The for the /// readonly Api.Models.Instance instance; + /// /// The for /// @@ -55,10 +58,12 @@ namespace Tgstation.Server.Host.Components.Compiler /// representing calls to /// Task cleanupTask; + /// /// resulting in the latest yet to exist /// TaskCompletionSource newerDmbTcs; + /// /// The latest /// @@ -87,7 +92,7 @@ namespace Tgstation.Server.Host.Components.Compiler } /// - public void Dispose() => cleanupCts.Dispose(); //we don't dispose nextDmbProvider here, since it might be the only thing we have + public void Dispose() => cleanupCts.Dispose(); // we don't dispose nextDmbProvider here, since it might be the only thing we have /// /// Delete the of @@ -99,12 +104,13 @@ namespace Tgstation.Server.Host.Components.Compiler { var deleteJob = ioManager.DeleteDirectory(job.DirectoryName.ToString(), cleanupCts.Token); Task otherTask; - //lock (this) //already locked below + + // lock (this) //already locked below otherTask = cleanupTask; await Task.WhenAll(otherTask, deleteJob).ConfigureAwait(false); } + lock (this) - { if (!jobLockCounts.TryGetValue(job.Id, out var currentVal) || currentVal == 1) { jobLockCounts.Remove(job.Id); @@ -116,7 +122,6 @@ namespace Tgstation.Server.Host.Components.Compiler var decremented = --jobLockCounts[job.Id]; logger.LogTrace("Compile job {0} lock count now: {1}", job.Id, decremented); } - } } /// @@ -156,13 +161,14 @@ namespace Tgstation.Server.Host.Components.Compiler /// public Task StartAsync(CancellationToken cancellationToken) => databaseContextFactory.UseContext(async (db) => { - //where complete clause not necessary, only successful COMPILEjobs get in the db + // where complete clause not necessary, only successful COMPILEjobs get in the db var cj = await db.CompileJobs.Where(x => x.Job.Instance.Id == instance.Id) .OrderByDescending(x => x.Job.StoppedAt).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (cj == default(CompileJob)) return; await LoadCompileJob(cj, cancellationToken).ConfigureAwait(false); - //we dont do CleanUnusedCompileJobs here because the watchdog may have plans for them yet + + // we dont do CleanUnusedCompileJobs here because the watchdog may have plans for them yet }); /// @@ -173,17 +179,18 @@ namespace Tgstation.Server.Host.Components.Compiler } /// + #pragma warning disable CA1506 // TODO: Decomplexify public async Task FromCompileJob(CompileJob compileJob, CancellationToken cancellationToken) { if (compileJob == null) throw new ArgumentNullException(nameof(compileJob)); - //ensure we have the entire compile job tree + // ensure we have the entire compile job tree await databaseContextFactory.UseContext(async db => compileJob = await db.CompileJobs.Where(x => x.Id == compileJob.Id) .Include(x => x.Job).ThenInclude(x => x.StartedBy) .Include(x => x.RevisionInformation).ThenInclude(x => x.PrimaryTestMerge).ThenInclude(x => x.MergedBy) .Include(x => x.RevisionInformation).ThenInclude(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).ThenInclude(x => x.MergedBy) - .FirstAsync(cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); //can't wait to see that query + .FirstAsync(cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); // can't wait to see that query logger.LogTrace("Loading compile job {0}...", compileJob.Id); var providerSubmitted = false; @@ -201,7 +208,7 @@ namespace Tgstation.Server.Host.Components.Compiler if (!(await primaryCheckTask.ConfigureAwait(false) && await secondaryCheckTask.ConfigureAwait(false))) { logger.LogWarning("Error loading compile job, .dmb missing!"); - return null; //omae wa mou shinderu + return null; // omae wa mou shinderu } lock (this) @@ -226,28 +233,31 @@ namespace Tgstation.Server.Host.Components.Compiler newProvider.Dispose(); } } + #pragma warning restore CA1506 /// + #pragma warning disable CA1506 // TODO: Decomplexify public async Task CleanUnusedCompileJobs(CompileJob exceptThisOne, CancellationToken cancellationToken) { List jobIdsToSkip; - //don't clean locked directories + + // don't clean locked directories lock (this) jobIdsToSkip = jobLockCounts.Select(x => x.Key).ToList(); List jobUidsToNotErase = null; - //find the uids of locked directories + // find the uids of locked directories await databaseContextFactory.UseContext(async db => { jobUidsToNotErase = await db.CompileJobs.Where(x => x.Job.Instance.Id == instance.Id && jobIdsToSkip.Contains(x.Id)).Select(x => x.DirectoryName.Value.ToString().ToUpperInvariant()).ToListAsync(cancellationToken).ConfigureAwait(false); }).ConfigureAwait(false); - //add the other exemption + // add the other exemption if (exceptThisOne != null) jobUidsToNotErase.Add(exceptThisOne.DirectoryName.Value.ToString().ToUpperInvariant()); - //cleanup + // cleanup await ioManager.CreateDirectory(".", cancellationToken).ConfigureAwait(false); var directories = await ioManager.GetDirectories(".", cancellationToken).ConfigureAwait(false); int deleting = 0; @@ -276,5 +286,6 @@ namespace Tgstation.Server.Host.Components.Compiler await Task.WhenAll().ConfigureAwait(false); } } + #pragma warning restore CA1506 } } diff --git a/src/Tgstation.Server.Host/Components/Compiler/DmbProvider.cs b/src/Tgstation.Server.Host/Components/Compiler/DmbProvider.cs index bf042d1912..bca0d73fbf 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DmbProvider.cs @@ -16,9 +16,6 @@ namespace Tgstation.Server.Host.Components.Compiler /// public string SecondaryDirectory => ioManager.ResolvePath(ioManager.ConcatPath(CompileJob.DirectoryName.ToString(), DreamMaker.BDirectoryName)); - /// - public RevisionInformation RevisionInformation => CompileJob.RevisionInformation; - /// /// The for the /// @@ -28,6 +25,7 @@ namespace Tgstation.Server.Host.Components.Compiler /// The for the /// readonly IIOManager ioManager; + /// /// The to run when is called /// diff --git a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs index 117edf4b3d..b4bebe5427 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs @@ -24,14 +24,17 @@ namespace Tgstation.Server.Host.Components.Compiler /// Name of the primary directory used for compilation /// public const string ADirectoryName = "A"; + /// /// Name of the secondary directory used for compilation /// public const string BDirectoryName = "B"; + /// /// Extension for .dmbs /// public const string DmbExtension = ".dmb"; + /// /// Extension for .dmes /// @@ -41,42 +44,52 @@ namespace Tgstation.Server.Host.Components.Compiler /// The for /// readonly IByondManager byond; + /// /// The for /// readonly IIOManager ioManager; + /// /// The for /// readonly StaticFiles.IConfiguration configuration; + /// /// The for /// readonly ISessionControllerFactory sessionControllerFactory; + /// /// The for /// readonly ICompileJobConsumer compileJobConsumer; + /// /// The for /// readonly IApplication application; + /// /// The for /// readonly IEventConsumer eventConsumer; + /// /// The for /// readonly IChat chat; + /// /// The for /// readonly IProcessExecutor processExecutor; + /// /// The for /// readonly IWatchdog watchdog; + /// /// The for /// @@ -133,13 +146,13 @@ namespace Tgstation.Server.Host.Components.Compiler { AllowWebClient = false, PrimaryPort = portToUse, - SecurityLevel = securityLevel, //all it needs to read the file and exit + SecurityLevel = securityLevel, StartupTimeout = timeout }; var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); - job.MinimumSecurityLevel = securityLevel; //needed for the TempDmbProvider + job.MinimumSecurityLevel = securityLevel; // needed for the TempDmbProvider var provider = new TemporaryDmbProvider(ioManager.ResolvePath(dirA), String.Concat(job.DmeName, DmbExtension), job); var timeoutAt = DateTimeOffset.Now.AddSeconds(timeout); @@ -184,13 +197,13 @@ namespace Tgstation.Server.Host.Components.Compiler throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Session controller returned unexpected ApiValidationStatus: {0}", validationStatus)); } } - + throw new JobException("DMAPI validation timed out!"); } } /// - /// Compiles a .dme with DreamMaker + /// Compiles a .dme with DreamMaker /// /// The path to the DreamMaker executable /// The for the operation @@ -267,6 +280,7 @@ namespace Tgstation.Server.Host.Components.Compiler } /// + #pragma warning disable CA1506 //TODO: Decomplexify public async Task Compile(Models.RevisionInformation revisionInformation, Api.Models.DreamMaker dreamMakerSettings, uint apiValidateTimeout, IRepository repository, Action progressReporter, TimeSpan? estimatedDuration, CancellationToken cancellationToken) { if (revisionInformation == null) @@ -327,8 +341,6 @@ namespace Tgstation.Server.Host.Components.Compiler var progressTask = ProgressTask(); try { - - var commitInsert = revisionInformation.CommitSha.Substring(0, 7); string remoteCommitInsert; if (revisionInformation.CommitSha == revisionInformation.OriginCommitSha) @@ -366,7 +378,7 @@ namespace Tgstation.Server.Host.Components.Compiler } await chatTask.ConfigureAwait(false); - }; + } try { @@ -375,18 +387,18 @@ namespace Tgstation.Server.Host.Components.Compiler var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); var dirB = ioManager.ConcatPath(job.DirectoryName.ToString(), BDirectoryName); + // copy the repository logger.LogTrace("Copying repository to game directory..."); - //copy the repository var fullDirA = ioManager.ResolvePath(dirA); var repoOrigin = repository.Origin; using (repository) await repository.CopyTo(fullDirA, cancellationToken).ConfigureAwait(false); - //run precompile scripts + // run precompile scripts var resolvedGameDirectory = ioManager.ResolvePath(ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName)); await eventConsumer.HandleEvent(EventType.CompileStart, new List { resolvedGameDirectory, repoOrigin }, cancellationToken).ConfigureAwait(false); - //determine the dme + // determine the dme if (job.DmeName == null) { logger.LogTrace("Searching for available .dmes..."); @@ -403,11 +415,12 @@ namespace Tgstation.Server.Host.Components.Compiler await ModifyDme(job, cancellationToken).ConfigureAwait(false); - //run compiler, verify api + // run compiler job.ByondVersion = byondLock.Version.ToString(); var exitCode = await RunDreamMaker(byondLock.DreamMakerPath, job, cancellationToken).ConfigureAwait(false); + // verify api try { if (exitCode != 0) @@ -417,7 +430,7 @@ namespace Tgstation.Server.Host.Components.Compiler } catch (JobException) { - //server never validated or compile failed + // DD never validated or compile failed await eventConsumer.HandleEvent(EventType.CompileFailure, new List { resolvedGameDirectory, exitCode == 0 ? "1" : "0" }, cancellationToken).ConfigureAwait(false); throw; } @@ -427,12 +440,12 @@ namespace Tgstation.Server.Host.Components.Compiler logger.LogTrace("Duplicating compiled game..."); - //duplicate the dmb et al + // duplicate the dmb et al await ioManager.CopyDirectory(dirA, dirB, null, cancellationToken).ConfigureAwait(false); logger.LogTrace("Applying static game file symlinks..."); - //symlink in the static data + // symlink in the static data var symATask = configuration.SymlinkStaticFilesTo(fullDirA, cancellationToken); var symBTask = configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken); @@ -463,5 +476,6 @@ namespace Tgstation.Server.Host.Components.Compiler } } } + #pragma warning restore CA1506 } } diff --git a/src/Tgstation.Server.Host/Components/Compiler/IDmbFactory.cs b/src/Tgstation.Server.Host/Components/Compiler/IDmbFactory.cs index f06ebd066b..fc153bb22c 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/IDmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/IDmbFactory.cs @@ -43,6 +43,5 @@ namespace Tgstation.Server.Host.Components.Compiler /// The for the operation /// A representing the running operation Task CleanUnusedCompileJobs(CompileJob exceptThisOne, CancellationToken cancellationToken); - } } diff --git a/src/Tgstation.Server.Host/Components/Compiler/TemporaryDmbProvider.cs b/src/Tgstation.Server.Host/Components/Compiler/TemporaryDmbProvider.cs index 3cd0962106..628f77eb0b 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/TemporaryDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/TemporaryDmbProvider.cs @@ -36,6 +36,7 @@ namespace Tgstation.Server.Host.Components.Compiler /// public void Dispose() { } + /// public void KeepAlive() => throw new NotSupportedException(); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/EventConsumer.cs b/src/Tgstation.Server.Host/Components/EventConsumer.cs index e28d93051f..e204cbcf8f 100644 --- a/src/Tgstation.Server.Host/Components/EventConsumer.cs +++ b/src/Tgstation.Server.Host/Components/EventConsumer.cs @@ -16,7 +16,7 @@ namespace Tgstation.Server.Host.Components readonly IConfiguration configuration; /// - /// The for the + /// The for the /// IWatchdog watchdog; @@ -46,8 +46,10 @@ namespace Tgstation.Server.Host.Components /// The value of public void SetWatchdog(IWatchdog watchdog) { +#pragma warning disable IDE0016 // Use 'throw' expression if (watchdog == null) throw new ArgumentNullException(nameof(watchdog)); +#pragma warning restore IDE0016 // Use 'throw' expression if (this.watchdog != null) throw new InvalidOperationException("watchdog already set!"); this.watchdog = watchdog; diff --git a/src/Tgstation.Server.Host/Components/EventType.cs b/src/Tgstation.Server.Host/Components/EventType.cs index c9da72aa68..f711742dbe 100644 --- a/src/Tgstation.Server.Host/Components/EventType.cs +++ b/src/Tgstation.Server.Host/Components/EventType.cs @@ -9,18 +9,22 @@ /// Parameters: Reference name, commit sha /// RepoResetOrigin = 0, + /// /// Parameters: Checkout target /// RepoCheckout = 1, + /// /// No parameters /// RepoFetch = 2, + /// /// Parameters: Pull request number, pull request sha, merger message /// RepoMergePullRequest = 3, + /// /// Parameters: Absolute path to repository root /// @@ -30,26 +34,32 @@ /// Parameters: Version being installed /// ByondInstallStart = 5, + /// /// Parameters: Error string /// ByondInstallFail = 6, + /// /// Parameters: Old active version, new active version /// ByondActiveVersionChange = 7, + /// /// Parameters: Game directory path, origin commit sha /// CompileStart = 8, + /// /// No parameters /// CompileCancelled = 9, + /// /// Parameters: Game directory path, "1" if compile succeeded and api validation failed, "0" otherwise /// CompileFailure = 10, + /// /// Parameters: Game directory path /// diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs index d90826f9b4..299dbe6fc0 100644 --- a/src/Tgstation.Server.Host/Components/IInstance.cs +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -25,7 +25,7 @@ namespace Tgstation.Server.Host.Components /// The for the /// IByondManager ByondManager { get; } - + /// /// The for the /// @@ -37,7 +37,7 @@ namespace Tgstation.Server.Host.Components IChat Chat { get; } /// - /// The for the + /// The for the /// IConfiguration Configuration { get; } diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 66597bc572..af4366011a 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -19,6 +19,7 @@ using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components { /// + #pragma warning disable CA1506 // TODO: Decomplexify sealed class Instance : IInstance { /// @@ -27,9 +28,6 @@ namespace Tgstation.Server.Host.Components /// public IByondManager ByondManager { get; } - /// - public IDreamMaker DreamMaker { get; } - /// public IWatchdog Watchdog { get; } @@ -39,8 +37,15 @@ namespace Tgstation.Server.Host.Components /// public StaticFiles.IConfiguration Configuration { get; } - /// - public ICompileJobConsumer CompileJobConsumer { get; } + /// + /// The for the + /// + readonly IDreamMaker dreamMaker; + + /// + /// The for the + /// + readonly ICompileJobConsumer compileJobConsumer; /// /// The for the @@ -81,6 +86,7 @@ namespace Tgstation.Server.Host.Components /// The auto update /// Task timerTask; + /// /// for /// @@ -92,11 +98,11 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - /// The value of + /// The value of /// The value of /// The value of /// The value of - /// The value of + /// The value of /// The value of /// The value of /// The value of @@ -108,11 +114,11 @@ namespace Tgstation.Server.Host.Components this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); RepositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager)); ByondManager = byondManager ?? throw new ArgumentNullException(nameof(byondManager)); - DreamMaker = dreamMaker ?? throw new ArgumentNullException(nameof(dreamMaker)); + this.dreamMaker = dreamMaker ?? throw new ArgumentNullException(nameof(dreamMaker)); Watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog)); Chat = chat ?? throw new ArgumentNullException(nameof(chat)); Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); - CompileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer)); + this.compileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory)); this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); @@ -125,7 +131,7 @@ namespace Tgstation.Server.Host.Components public void Dispose() { timerCts?.Dispose(); - CompileJobConsumer.Dispose(); + compileJobConsumer.Dispose(); Configuration.Dispose(); Chat.Dispose(); Watchdog.Dispose(); @@ -135,9 +141,10 @@ namespace Tgstation.Server.Host.Components /// public async Task CompileProcess(Job job, IDatabaseContext databaseContext, Action progressReporter, CancellationToken cancellationToken) { - //DO NOT FOLLOW THE SUGGESTION FOR A THROW EXPRESSION HERE +#pragma warning disable IDE0016 // Use 'throw' expression if (job == null) throw new ArgumentNullException(nameof(job)); +#pragma warning restore IDE0016 // Use 'throw' expression if (databaseContext == null) throw new ArgumentNullException(nameof(databaseContext)); if (progressReporter == null) @@ -151,8 +158,6 @@ namespace Tgstation.Server.Host.Components var compileJobsTask = databaseContext.CompileJobs .Where(x => x.Job.Instance.Id == metadata.Id) .OrderByDescending(x => x.Job.StoppedAt) - //TODO: Replace with this select when the issues linked in https://github.com/tgstation/tgstation-server/issues/737 are fixed - //.Select(x => x.Job.StoppedAt.Value - x.Job.StartedAt.Value) .Select(x => new Job { StoppedAt = x.Job.StoppedAt, @@ -206,7 +211,7 @@ namespace Tgstation.Server.Host.Components logger.LogWarning(Repository.Repository.OriginTrackingErrorTemplate, repoSha); databaseContext.Instances.Attach(revInfo.Instance); } - + TimeSpan? averageSpan = null; var previousCompileJobs = await compileJobsTask.ConfigureAwait(false); if(previousCompileJobs.Count != 0) @@ -217,14 +222,14 @@ namespace Tgstation.Server.Host.Components averageSpan = totalSpan / previousCompileJobs.Count; } - compileJob = await DreamMaker.Compile(revInfo, dreamMakerSettings, ddSettings.StartupTimeout.Value, repo, progressReporter, averageSpan, cancellationToken).ConfigureAwait(false); + compileJob = await dreamMaker.Compile(revInfo, dreamMakerSettings, ddSettings.StartupTimeout.Value, repo, progressReporter, averageSpan, cancellationToken).ConfigureAwait(false); } compileJob.Job = job; - databaseContext.CompileJobs.Add(compileJob); //will be saved by job context - - job.PostComplete = ct => CompileJobConsumer.LoadCompileJob(compileJob, ct); + databaseContext.CompileJobs.Add(compileJob); // will be saved by job context + + job.PostComplete = ct => compileJobConsumer.LoadCompileJob(compileJob, ct); if (repositorySettingsTask != null) { @@ -234,10 +239,10 @@ namespace Tgstation.Server.Host.Components if (repositorySettings.AccessToken != null) { - //potential for commenting on a test merge change + // potential for commenting on a test merge change var outgoingCompileJob = LatestCompileJob(); - - if(outgoingCompileJob != null && outgoingCompileJob.RevisionInformation.CommitSha != compileJob.RevisionInformation.CommitSha) + + if(outgoingCompileJob != null && outgoingCompileJob.RevisionInformation.CommitSha != compileJob.RevisionInformation.CommitSha && repositorySettings.PostTestMergeComment.Value) { var gitHubClient = gitHubClientFactory.CreateClient(repositorySettings.AccessToken); @@ -253,58 +258,53 @@ namespace Tgstation.Server.Host.Components } } - if (repositorySettings.PostTestMergeComment.Value) - { + var tasks = new List(); - var tasks = new List(); + string FormatTestMerge(TestMerge testMerge, bool updated) => String.Format(CultureInfo.InvariantCulture, "#### Test Merge {4}{0}{0}##### Server Instance{0}{5}{1}{0}{0}##### Revision{0}Origin: {6}{0}Pull Request: {2}{0}Server: {7}{3}", + Environment.NewLine, + repositorySettings.ShowTestMergeCommitters.Value ? String.Format(CultureInfo.InvariantCulture, "{0}{0}##### Merged By{0}{1}", Environment.NewLine, testMerge.MergedBy.Name) : String.Empty, + testMerge.PullRequestRevision, + testMerge.Comment != null ? String.Format(CultureInfo.InvariantCulture, "{0}{0}##### Comment{0}{1}", Environment.NewLine, testMerge.Comment) : String.Empty, + updated ? "Updated" : "Deployed", + metadata.Name, + compileJob.RevisionInformation.OriginCommitSha, + compileJob.RevisionInformation.CommitSha); - string FormatTestMerge(TestMerge testMerge, bool updated) => String.Format(CultureInfo.InvariantCulture, "#### Test Merge {4}{0}{0}##### Server Instance{0}{5}{1}{0}{0}##### Revision{0}Origin: {6}{0}Pull Request: {2}{0}Server: {7}{3}", - Environment.NewLine, - repositorySettings.ShowTestMergeCommitters.Value ? String.Format(CultureInfo.InvariantCulture, "{0}{0}##### Merged By{0}{1}", Environment.NewLine, testMerge.MergedBy.Name) : String.Empty, - testMerge.PullRequestRevision, - testMerge.Comment != null ? String.Format(CultureInfo.InvariantCulture, "{0}{0}##### Comment{0}{1}", Environment.NewLine, testMerge.Comment) : String.Empty, - updated ? "Updated" : "Deployed", - metadata.Name, - compileJob.RevisionInformation.OriginCommitSha, - compileJob.RevisionInformation.CommitSha - ); - - //added prs - foreach (var I in compileJob + // added prs + foreach (var I in compileJob + .RevisionInformation + .ActiveTestMerges + .Select(x => x.TestMerge) + .Where(x => !outgoingCompileJob .RevisionInformation .ActiveTestMerges - .Select(x => x.TestMerge) - .Where(x => !outgoingCompileJob - .RevisionInformation - .ActiveTestMerges - .Any(y => y.TestMerge.Number == x.Number))) - tasks.Add(CommentOnPR(I.Number.Value, FormatTestMerge(I, false))); + .Any(y => y.TestMerge.Number == x.Number))) + tasks.Add(CommentOnPR(I.Number.Value, FormatTestMerge(I, false))); - //removed prs - foreach (var I in outgoingCompileJob + // removed prs + foreach (var I in outgoingCompileJob + .RevisionInformation + .ActiveTestMerges + .Select(x => x.TestMerge) + .Where(x => !compileJob .RevisionInformation .ActiveTestMerges - .Select(x => x.TestMerge) - .Where(x => !compileJob - .RevisionInformation - .ActiveTestMerges - .Any(y => y.TestMerge.Number == x.Number))) - tasks.Add(CommentOnPR(I.Number.Value, "#### Test Merge Removed")); + .Any(y => y.TestMerge.Number == x.Number))) + tasks.Add(CommentOnPR(I.Number.Value, "#### Test Merge Removed")); - //updated prs - foreach (var I in compileJob + // updated prs + foreach(var I in compileJob + .RevisionInformation + .ActiveTestMerges + .Select(x => x.TestMerge) + .Where(x => outgoingCompileJob .RevisionInformation .ActiveTestMerges - .Select(x => x.TestMerge) - .Where(x => outgoingCompileJob - .RevisionInformation - .ActiveTestMerges - .Any(y => y.TestMerge.Number == x.Number))) - tasks.Add(CommentOnPR(I.Number.Value, FormatTestMerge(I, true))); + .Any(y => y.TestMerge.Number == x.Number))) + tasks.Add(CommentOnPR(I.Number.Value, FormatTestMerge(I, true))); - if (tasks.Any()) - await Task.WhenAll(tasks).ConfigureAwait(false); - } + if (tasks.Any()) + await Task.WhenAll(tasks).ConfigureAwait(false); } } } @@ -316,6 +316,7 @@ namespace Tgstation.Server.Host.Components /// How many minutes the operation should repeat. Does not include running time /// The for the operation /// A representing the running operation + #pragma warning disable CA1502 // TODO: Decomplexify async Task TimerLoop(uint minutes, CancellationToken cancellationToken) { while (true) @@ -345,11 +346,10 @@ namespace Tgstation.Server.Host.Components { var repositorySettingsTask = databaseContext.RepositorySettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(jobCancellationToken); - //assume 5 steps with synchronize + // assume 5 steps with synchronize const int ProgressSections = 7; const int ProgressStep = 100 / ProgressSections; - const int NumSteps = 3; var doneSteps = 0; @@ -357,8 +357,8 @@ namespace Tgstation.Server.Host.Components { var tmpDoneSteps = doneSteps; ++doneSteps; - return progress => progressReporter((progress + 100 * tmpDoneSteps) / NumSteps); - }; + return progress => progressReporter((progress + (100 * tmpDoneSteps)) / NumSteps); + } using (var repo = await RepositoryManager.LoadRepository(jobCancellationToken).ConfigureAwait(false)) { @@ -378,7 +378,7 @@ namespace Tgstation.Server.Host.Components var repositorySettings = await repositorySettingsTask.ConfigureAwait(false); - //the main point of auto update is to pull the remote + // the main point of auto update is to pull the remote await repo.FetchOrigin(repositorySettings.AccessUser, repositorySettings.AccessToken, NextProgressReporter(), jobCancellationToken).ConfigureAwait(false); RevisionInformation currentRevInfo = null; @@ -419,7 +419,7 @@ namespace Tgstation.Server.Host.Components hasDbChanges = true; } - //take appropriate auto update actions + // take appropriate auto update actions bool shouldSyncTracked; if (repositorySettings.AutoUpdatesKeepTestMerges.Value) { @@ -463,7 +463,7 @@ namespace Tgstation.Server.Host.Components shouldSyncTracked = true; } - //synch if necessary + // synch if necessary if (repositorySettings.AutoUpdatesSynchronize.Value && startSha != repo.Head) { var pushedOrigin = await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), shouldSyncTracked, jobCancellationToken).ConfigureAwait(false); @@ -502,7 +502,7 @@ namespace Tgstation.Server.Host.Components continue; } - //finally set up the job + // finally set up the job var compileProcessJob = new Job { StartedBy = user, @@ -531,9 +531,11 @@ namespace Tgstation.Server.Host.Components { break; } + logger.LogTrace("Leaving auto update loop..."); } - + #pragma warning restore CA1502 + /// public void Rename(string newName) { @@ -545,9 +547,9 @@ namespace Tgstation.Server.Host.Components /// public async Task StartAsync(CancellationToken cancellationToken) { - await Task.WhenAll(SetAutoUpdateInterval(metadata.AutoUpdateInterval.Value), Configuration.StartAsync(cancellationToken), ByondManager.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), CompileJobConsumer.StartAsync(cancellationToken)).ConfigureAwait(false); + await Task.WhenAll(SetAutoUpdateInterval(metadata.AutoUpdateInterval.Value), Configuration.StartAsync(cancellationToken), ByondManager.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken)).ConfigureAwait(false); - //dependent on so many things, its just safer this way + // dependent on so many things, its just safer this way await Watchdog.StartAsync(cancellationToken).ConfigureAwait(false); CompileJob latestCompileJob = null; @@ -559,7 +561,7 @@ namespace Tgstation.Server.Host.Components } /// - public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(0), Configuration.StopAsync(cancellationToken), ByondManager.StopAsync(cancellationToken), Watchdog.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), CompileJobConsumer.StopAsync(cancellationToken)); + public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(0), Configuration.StopAsync(cancellationToken), ByondManager.StopAsync(cancellationToken), Watchdog.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken)); /// public async Task SetAutoUpdateInterval(uint newInterval) @@ -575,12 +577,13 @@ namespace Tgstation.Server.Host.Components else toWait = Task.CompletedTask; } + await toWait.ConfigureAwait(false); if (newInterval == 0) return; lock (this) { - //race condition, just quit + // race condition, just quit if (timerTask != null) return; timerCts?.Dispose(); diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index b54cbc4d68..cbd14f17b6 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -152,12 +152,13 @@ namespace Tgstation.Server.Host.Components } /// + #pragma warning disable CA1506 // TODO: Decomplexify public IInstance CreateInstance(Models.Instance metadata) { - //Create the ioManager for the instance + // Create the ioManager for the instance var instanceIoManager = new ResolvingIOManager(ioManager, metadata.Path); - //various other ioManagers + // various other ioManagers var repoIoManager = new ResolvingIOManager(instanceIoManager, "Repository"); var byondIOManager = new ResolvingIOManager(instanceIoManager, "Byond"); var gameIoManager = new ResolvingIOManager(instanceIoManager, "Game"); @@ -214,6 +215,7 @@ namespace Tgstation.Server.Host.Components throw; } } + #pragma warning restore CA1506 /// public Task StartAsync(CancellationToken cancellationToken) => byondInstaller.CleanCache(cancellationToken); diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 88a2286d47..f8f63206ff 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -12,7 +12,7 @@ using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components { /// - sealed class InstanceManager : IInstanceManager, IHostedService, IDisposable + sealed class InstanceManager : IInstanceManager, IRestartHandler, IHostedService, IDisposable { /// /// The for the @@ -39,6 +39,11 @@ namespace Tgstation.Server.Host.Components /// readonly IJobManager jobManager; + /// + /// The for the + /// + readonly IServerControl serverControl; + /// /// The for the /// @@ -49,6 +54,11 @@ namespace Tgstation.Server.Host.Components /// readonly Dictionary instances; + /// + /// Used in to determine if database downgrades must be made + /// + Version downgradeVersion; + /// /// If the has been d /// @@ -62,16 +72,20 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of + /// The value of /// The value of - public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, IJobManager jobManager, ILogger logger) + public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, IJobManager jobManager, IServerControl serverControl, ILogger logger) { this.instanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.application = application ?? throw new ArgumentNullException(nameof(application)); this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); + this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + serverControl.RegisterForRestart(this); + instances = new Dictionary(); } @@ -84,6 +98,7 @@ namespace Tgstation.Server.Host.Components return; disposed = true; } + foreach (var I in instances) I.Value.Dispose(); } @@ -136,9 +151,10 @@ namespace Tgstation.Server.Host.Components throw new InvalidOperationException("Instance not online!"); instances.Remove(metadata.Id); } + try { - //we are the one responsible for cancelling his jobs + // we are the one responsible for cancelling his jobs var tasks = new List(); await databaseContextFactory.UseContext(async db => { @@ -184,10 +200,12 @@ namespace Tgstation.Server.Host.Components instance.Dispose(); throw; } + await instance.StartAsync(cancellationToken).ConfigureAwait(false); } /// + #pragma warning disable CA1506 // TODO: Decomplexify public Task StartAsync(CancellationToken cancellationToken) => databaseContextFactory.UseContext(async databaseContext => { try @@ -205,7 +223,7 @@ namespace Tgstation.Server.Host.Components await factoryStartup.ConfigureAwait(false); await dbInstances.ForEachAsync(metadata => tasks.Add(metadata.Online.Value ? OnlineInstance(metadata, cancellationToken) : Task.CompletedTask), cancellationToken).ConfigureAwait(false); await Task.WhenAll(tasks).ConfigureAwait(false); - logger.LogInformation("Instance manager ready!"); + logger.LogInformation("Server ready!"); application.Ready(null); } catch (OperationCanceledException) @@ -216,8 +234,17 @@ namespace Tgstation.Server.Host.Components { logger.LogCritical("Instance manager startup error! Exception: {0}", e); application.Ready(e); + try + { + await serverControl.Die(e).ConfigureAwait(false); + } + catch (Exception e2) + { + logger.LogCritical("Failed to kill server! Exception: {0}", e2); + } } }); + #pragma warning restore CA1506 // TODO: Decomplexify /// public async Task StopAsync(CancellationToken cancellationToken) @@ -225,6 +252,17 @@ namespace Tgstation.Server.Host.Components await jobManager.StopAsync(cancellationToken).ConfigureAwait(false); await Task.WhenAll(instances.Select(x => x.Value.StopAsync(cancellationToken))).ConfigureAwait(false); await instanceFactory.StopAsync(cancellationToken).ConfigureAwait(false); + + // downgrade the db if necessary + if (downgradeVersion != null) + await databaseContextFactory.UseContext(db => db.SchemaDowngradeForServerVersion(downgradeVersion, cancellationToken)).ConfigureAwait(false); + } + + /// + public Task HandleRestart(Version updateVersion, CancellationToken cancellationToken) + { + downgradeVersion = updateVersion != null && updateVersion < application.Version ? updateVersion : null; + return Task.CompletedTask; } } } diff --git a/src/Tgstation.Server.Host/Components/Interop/ChatCommand.cs b/src/Tgstation.Server.Host/Components/Interop/ChatCommand.cs index 1c49476b35..fbad20d56e 100644 --- a/src/Tgstation.Server.Host/Components/Interop/ChatCommand.cs +++ b/src/Tgstation.Server.Host/Components/Interop/ChatCommand.cs @@ -2,10 +2,24 @@ namespace Tgstation.Server.Host.Components.Interop { + /// + /// Represents a chat command to be handled by DD + /// sealed class ChatCommand { + /// + /// The command name + /// public string Command { get; set; } - public string Parameters { get; set; } + + /// + /// The command params + /// + public string Params { get; set; } + + /// + /// The that sent the command + /// public User User { get; set; } } } diff --git a/src/Tgstation.Server.Host/Components/Interop/CommCommand.cs b/src/Tgstation.Server.Host/Components/Interop/CommCommand.cs index 7145d35aae..31b4715897 100644 --- a/src/Tgstation.Server.Host/Components/Interop/CommCommand.cs +++ b/src/Tgstation.Server.Host/Components/Interop/CommCommand.cs @@ -10,7 +10,7 @@ namespace Tgstation.Server.Host.Components.Interop /// /// The dictionary of the /// - public IReadOnlyDictionary Parameters { get; set; } + public IReadOnlyDictionary Parameters { get; set; } /// /// The raw JSON of the diff --git a/src/Tgstation.Server.Host/Components/Interop/CommContext.cs b/src/Tgstation.Server.Host/Components/Interop/CommContext.cs index f9c5208d88..489053e91b 100644 --- a/src/Tgstation.Server.Host/Components/Interop/CommContext.cs +++ b/src/Tgstation.Server.Host/Components/Interop/CommContext.cs @@ -95,7 +95,7 @@ namespace Tgstation.Server.Host.Components.Interop /// /// The sender of the event /// The - async void HandleWrite(object sender, FileSystemEventArgs e) //this is what async void was made for + async void HandleWrite(object sender, FileSystemEventArgs e) // this is what async void was made for { try { @@ -109,13 +109,13 @@ namespace Tgstation.Server.Host.Components.Interop { command = new CommCommand { - Parameters = JsonConvert.DeserializeObject>(file), + Parameters = JsonConvert.DeserializeObject>(file), RawJson = file }; } catch (JsonException ex) { - //file not fully written yet + // file not fully written yet logger.LogDebug("Suppressing json convert exception for command file write: {0}", ex); return; } diff --git a/src/Tgstation.Server.Host/Components/Interop/Constants.cs b/src/Tgstation.Server.Host/Components/Interop/Constants.cs index 834a43063f..da5da51c91 100644 --- a/src/Tgstation.Server.Host/Components/Interop/Constants.cs +++ b/src/Tgstation.Server.Host/Components/Interop/Constants.cs @@ -1,31 +1,93 @@ namespace Tgstation.Server.Host.Components.Interop { + /// + /// Constants used for communication with the DMAPI + /// static class Constants { - //interop values, match them up with the appropriate api.dm - - //api version 4.0.0.0 + /// + /// Identifies a TGS execution. The server version + /// public const string DMParamHostVersion = "server_service_version"; + + /// + /// Path to the + /// public const string DMParamInfoJson = "tgs_json"; + /// + /// The + /// public const string DMInteropAccessIdentifier = "tgs_tok"; + /// + /// Generic OK response + /// public const string DMResponseSuccess = "tgs_succ"; + /// + /// Change port + /// public const string DMTopicChangePort = "tgs_port"; + + /// + /// Change reboot mode + /// public const string DMTopicChangeReboot = "tgs_rmode"; + + /// + /// Chat command + /// public const string DMTopicChatCommand = "tgs_chat_comm"; + + /// + /// Notify of an + /// public const string DMTopicEvent = "tgs_event"; + + /// + /// Response to an interop export from DM + /// public const string DMTopicInteropResponse = "tgs_interop"; + /// + /// Set port command + /// public const string DMCommandNewPort = "tgs_new_port"; + + /// + /// API validation command + /// public const string DMCommandApiValidate = "tgs_validate"; + + /// + /// Server primed command + /// public const string DMCommandServerPrimed = "tgs_prime"; + + /// + /// World reboot command + /// public const string DMCommandWorldReboot = "tgs_reboot"; + + /// + /// Terminate process command + /// public const string DMCommandEndProcess = "tgs_kill"; + + /// + /// Chat send command + /// public const string DMCommandChat = "tgs_chat_send"; + /// + /// Topic command parameter + /// public const string DMParameterCommand = "tgs_com"; + + /// + /// Command data + /// public const string DMParameterData = "tgs_data"; } } diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs index e993508172..44cb5b39db 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs @@ -70,8 +70,8 @@ namespace Tgstation.Server.Host.Components.Repository /// The e-mail of the merge committer /// The username to fetch from the origin repository /// The password to fetch from the origin repository - /// The for the operation /// to report 0-100 progress of the operation + /// The for the operation /// A resulting in a representing the merge result that is after a fast forward or up to date, on a non-fast-forward, on a conflict Task AddTestMerge(TestMergeParameters testMergeParameters, string committerName, string committerEmail, string username, string password, Action progressReporter, CancellationToken cancellationToken); @@ -119,8 +119,8 @@ namespace Tgstation.Server.Host.Components.Repository /// The password to fetch from the origin repository /// The name of the potential committer /// The e-mail of the potential committer - /// If the synchronizations should be made to the tracked reference as opposed to a temporary branch /// to report 0-100 progress of the operation + /// If the synchronizations should be made to the tracked reference as opposed to a temporary branch /// The for the operation /// A resulting in if commits were pushed to the tracked origin reference, otherwise Task Sychronize(string username, string password, string committerName, string committerEmail, Action progressReporter, bool synchronizeTrackedBranch, CancellationToken cancellationToken); diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index d9d0daaa8d..3363f1e8c8 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -84,27 +84,12 @@ namespace Tgstation.Server.Host.Components.Repository /// readonly Action onDispose; - void GetRepositoryOwnerName(string remote, out string owner, out string name) - { - //Assume standard gh format: [(git)|(https)]://github.com/owner/repo(.git)[0-1] - //Yes use .git twice in case it was weird - var toRemove = new string[] { ".git", "/", ".git" }; - foreach (string item in toRemove) - if (remote.EndsWith(item, StringComparison.OrdinalIgnoreCase)) - remote = remote.Substring(0, remote.LastIndexOf(item, StringComparison.OrdinalIgnoreCase)); - var splits = remote.Split('/'); - name = splits[splits.Length - 1]; - owner = splits[splits.Length - 2].Split('.')[0]; - - logger.LogTrace("GetRepositoryOwnerName({0}) => {1} / {2}", remote, owner, name); - } - /// /// Converts a given to a /// /// to report 0-100 progress of the operation /// A based on - static CheckoutProgressHandler CheckoutProgressHandler(Action progressReporter) => (a, completedSteps, totalSteps) => progressReporter((int)((((float)completedSteps) / totalSteps) * 100)); + static CheckoutProgressHandler CheckoutProgressHandler(Action progressReporter) => (a, completedSteps, totalSteps) => progressReporter((int)(((float)completedSteps) / totalSteps * 100)); /// /// Construct a @@ -140,6 +125,21 @@ namespace Tgstation.Server.Host.Components.Repository onDispose.Invoke(); } + void GetRepositoryOwnerName(string remote, out string owner, out string name) + { + // Assume standard gh format: [(git)|(https)]://github.com/owner/repo(.git)[0-1] + // Yes use .git twice in case it was weird + var toRemove = new string[] { ".git", "/", ".git" }; + foreach (string item in toRemove) + if (remote.EndsWith(item, StringComparison.OrdinalIgnoreCase)) + remote = remote.Substring(0, remote.LastIndexOf(item, StringComparison.OrdinalIgnoreCase)); + var splits = remote.Split('/'); + name = splits[splits.Length - 1]; + owner = splits[splits.Length - 2].Split('.')[0]; + + logger.LogTrace("GetRepositoryOwnerName({0}) => {1} / {2}", remote, owner, name); + } + /// /// Generate a standard set of /// @@ -190,6 +190,7 @@ namespace Tgstation.Server.Host.Components.Repository } /// + #pragma warning disable CA1506 // TODO: Decomplexify public async Task AddTestMerge(TestMergeParameters testMergeParameters, string committerName, string committerEmail, string username, string password, Action progressReporter, CancellationToken cancellationToken) { if (testMergeParameters == null) @@ -264,7 +265,7 @@ namespace Tgstation.Server.Host.Components.Repository FailOnConflict = true, FastForwardStrategy = FastForwardStrategy.NoFastForward, SkipReuc = true, - OnCheckoutProgress = (a, completedSteps, totalSteps) => progressReporter(50 + ((int)((((float)completedSteps) / totalSteps) * 50))) + OnCheckoutProgress = (a, completedSteps, totalSteps) => progressReporter(50 + ((int)(((float)completedSteps) / totalSteps * 50))) }); } finally @@ -304,6 +305,7 @@ namespace Tgstation.Server.Host.Components.Repository return result.Status != MergeStatus.NonFastForward; } + #pragma warning restore CA1506 /// public async Task CheckoutObject(string committish, Action progressReporter, CancellationToken cancellationToken) @@ -373,7 +375,7 @@ namespace Tgstation.Server.Host.Components.Repository try { var forcePushString = String.Format(CultureInfo.InvariantCulture, "+{0}:{0}", branch.CanonicalName); - repository.Network.Push(remote,forcePushString, GeneratePushOptions(progress => progressReporter((int)(0.9f * progress)), username, password, cancellationToken)); + repository.Network.Push(remote, forcePushString, GeneratePushOptions(progress => progressReporter((int)(0.9f * progress)), username, password, cancellationToken)); var removalString = String.Format(CultureInfo.InvariantCulture, ":{0}", branch.CanonicalName); repository.Network.Push(remote, removalString, GeneratePushOptions(progress => progressReporter(90 + (int)(0.1f * progress)), username, password, cancellationToken)); } @@ -510,6 +512,7 @@ namespace Tgstation.Server.Host.Components.Repository logger.LogTrace("Not synchronizing due to lack of credentials!"); return false; } + logger.LogTrace("Begin Synchronize..."); if (username == null) @@ -596,16 +599,18 @@ namespace Tgstation.Server.Host.Components.Repository /// public Task IsSha(string committish, CancellationToken cancellationToken) => Task.Factory.StartNew(() => { - //check if it's a tag + // check if it's a tag var gitObject = repository.Lookup(committish, ObjectType.Tag); if (gitObject != null) return false; cancellationToken.ThrowIfCancellationRequested(); - //check if it's a branch + + // check if it's a branch if (repository.Branches[committish] != null) return false; cancellationToken.ThrowIfCancellationRequested(); - //err on the side of references, if we can't look it up, assume its a reference + + // err on the side of references, if we can't look it up, assume its a reference if (repository.Lookup(committish) != null) return true; return false; diff --git a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs index 2ec1c57468..4feeb46897 100644 --- a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs @@ -95,6 +95,7 @@ namespace Tgstation.Server.Host.Components.Repository throw new InvalidOperationException("The repository is already being cloned!"); CloneInProgress = true; } + try { using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) @@ -139,6 +140,7 @@ namespace Tgstation.Server.Host.Components.Repository { logger.LogDebug("Error deleting partially cloned repository! Exception: {0}", e); } + throw; } else @@ -147,12 +149,14 @@ namespace Tgstation.Server.Host.Components.Repository return null; } } + logger.LogInformation("Clone complete!"); } finally { CloneInProgress = false; } + return await LoadRepository(cancellationToken).ConfigureAwait(false); } @@ -188,6 +192,7 @@ namespace Tgstation.Server.Host.Components.Repository semaphore.Release(); return null; } + return new Repository(repo, ioManager, eventConsumer, credentialsProvider, repositoryLogger, () => { logger.LogTrace("Releasing semaphore due to Repository disposal..."); diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs index f7a6436e22..4211761fed 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using System.Threading; @@ -131,12 +130,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles /// public async Task CopyDMFilesTo(string dmeFile, string destination, CancellationToken cancellationToken) { - using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { await EnsureDirectories(cancellationToken).ConfigureAwait(false); - //just assume no other fs race conditions here + // just assume no other fs race conditions here var dmeExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, dmeFile), cancellationToken); var headFileExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, CodeModificationsHeadFile), cancellationToken); var tailFileExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, CodeModificationsTailFile), cancellationToken); @@ -154,7 +152,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles return null; string IncludeLine(string filePath) => String.Format(CultureInfo.InvariantCulture, "#include \"{0}\"", filePath); - + return new ServerSideModifications(headFileExistsTask.Result ? IncludeLine(CodeModificationsHeadFile) : null, tailFileExistsTask.Result ? IncludeLine(CodeModificationsTailFile) : null, false); } } @@ -168,7 +166,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles configurationRelativePath = '.' + configurationRelativePath; var resolved = ioManager.ResolvePath(configurationRelativePath); var local = !nullOrEmptyCheck ? ioManager.ResolvePath(".") : null; - if (!nullOrEmptyCheck && resolved.Length < local.Length) //.. fuccbois + if (!nullOrEmptyCheck && resolved.Length < local.Length) // .. fuccbois throw new InvalidOperationException("Attempted to access file outside of configuration manager!"); return resolved; } @@ -201,6 +199,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles result = null; return; } + enumerator = synchronousIOManager.GetFiles(path, cancellationToken); result.AddRange(enumerator.Select(x => new ConfigurationFile { @@ -236,7 +235,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles #pragma warning disable CA5350 // Do not use insecure cryptographic algorithm SHA1. using (var sha1 = new SHA1Managed()) #pragma warning restore CA5350 // Do not use insecure cryptographic algorithm SHA1. - sha1String = String.Join("", sha1.ComputeHash(content).Select(b => b.ToString("x2", CultureInfo.InvariantCulture))); + sha1String = String.Join(String.Empty, sha1.ComputeHash(content).Select(b => b.ToString("x2", CultureInfo.InvariantCulture))); result = new ConfigurationFile { Content = content, @@ -248,7 +247,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles } catch (UnauthorizedAccessException) { - //this happens on windows, dunno about linux + // this happens on windows, dunno about linux bool isDirectory; try { @@ -289,7 +288,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles var results = new List { StaticIgnoreFile }; - //we don't want to lose trailing whitespace on linux + // we don't want to lose trailing whitespace on linux using (var reader = new StringReader(ignoreFileText)) { cancellationToken.ThrowIfCancellationRequested(); @@ -299,7 +298,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles } return results; - }; + } IReadOnlyList ignoreFiles; @@ -316,9 +315,9 @@ namespace Tgstation.Server.Host.Components.StaticFiles { var fileName = ioManager.GetFileName(x); + // need to normalize bool ignored; if (platformIdentifier.IsWindows) - //need to normalize ignored = ignoreFiles.Any(y => fileName.ToUpperInvariant() == y.ToUpperInvariant()); else ignored = ignoreFiles.Any(y => fileName == y); @@ -367,7 +366,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles if (!success) return; if (data != null) - postWriteHandler.HandleWrite(path); + postWriteHandler.HandleWrite(path); result = new ConfigurationFile { Content = data, @@ -379,7 +378,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles } catch (UnauthorizedAccessException) { - //this happens on windows, dunno about linux + // this happens on windows, dunno about linux bool isDirectory; try { @@ -442,7 +441,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles if (!EventTypeScriptFileNameMap.TryGetValue(eventType, out var scriptName)) return true; - //always execute in serial + // always execute in serial using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { var files = await ioManager.GetFilesWithExtension(EventScriptsSubdirectory, platformIdentifier.ScriptFileExtension, cancellationToken).ConfigureAwait(false); @@ -458,6 +457,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles return false; } } + return true; } @@ -466,7 +466,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles { await EnsureDirectories(cancellationToken).ConfigureAwait(false); var path = ValidateConfigRelativePath(configurationRelativePath); - + var result = false; using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { @@ -477,6 +477,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles else CheckDeleteImpl(); } + return result; } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ApiValidationStatus.cs b/src/Tgstation.Server.Host/Components/Watchdog/ApiValidationStatus.cs index 46e282802d..f830edb59e 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ApiValidationStatus.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ApiValidationStatus.cs @@ -9,22 +9,27 @@ /// The DMAPI never contacted the server for validation /// NeverValidated, + /// /// The server was contacted for validation but it was never requested /// UnaskedValidationRequest, + /// /// The validation request was malformed /// BadValidationRequest, + /// /// Valid API. The game must be run with a minimum security level of /// RequiresSafe, + /// /// Valid API. The game must be run with a security level of /// RequiresTrusted, + /// /// Valid API. The game must be run with a minimum security level of /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/DeadSessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/DeadSessionController.cs index c310f18d65..61a8a7cfc2 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/DeadSessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/DeadSessionController.cs @@ -72,6 +72,7 @@ namespace Tgstation.Server.Host.Components.Watchdog return; disposed = true; } + Dmb.Dispose(); } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/MonitorAction.cs b/src/Tgstation.Server.Host/Components/Watchdog/MonitorAction.cs index bf343bbced..306bdccc40 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/MonitorAction.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/MonitorAction.cs @@ -9,18 +9,22 @@ /// The monitor should continue as normal /// Continue, + /// - /// Skips the next call to HandleMonitorWakeup action + /// Skips the next call to HandleMonitorWakeup action /// Skip, + /// /// The monitor should kill and restart both servers /// Restart, + /// /// The monitor should stop checking actions for this iteration and continue its loop /// Break, + /// /// The monitor should exit. Does not kill servers /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs b/src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs index 8a3d842dca..2863028238 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs @@ -9,26 +9,32 @@ /// The active server crashed or exited /// ActiveServerCrashed, + /// /// The inactive server crashed or exited /// InactiveServerCrashed, + /// /// The active server called /world/Reboot() /// ActiveServerRebooted, + /// /// The inactive server called /world/Reboot() /// InactiveServerRebooted, + /// /// The inactive server is past that point where DD hangs when you press "Go" /// InactiveServerStartupComplete, + /// /// A new .dmb was deployed /// NewDmbAvailable, + /// /// Server launch parameters were changed /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/PosixNetworkPromptReaper.cs b/src/Tgstation.Server.Host/Components/Watchdog/PosixNetworkPromptReaper.cs index f12cfbc7b9..e69e8a360b 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/PosixNetworkPromptReaper.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/PosixNetworkPromptReaper.cs @@ -2,11 +2,13 @@ namespace Tgstation.Server.Host.Components.Watchdog { - //POSIX BYOND doesn't prompt you when you change the port /// sealed class PosixNetworkPromptReaper : INetworkPromptReaper { /// - public void RegisterProcess(IProcess process) { } + public void RegisterProcess(IProcess process) + { + // POSIX BYOND doesn't prompt you when you change the port + } } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/RebootState.cs b/src/Tgstation.Server.Host/Components/Watchdog/RebootState.cs index 70b4dac3f5..0dbc6e95aa 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/RebootState.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/RebootState.cs @@ -9,10 +9,12 @@ /// Run DreamDaemon's normal reboot process /// Normal = 0, + /// /// Shutdown DreamDaemon /// Shutdown = 1, + /// /// Restart the DreamDaemon process /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs index 8cd90b5b72..ac67ef21d4 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs @@ -136,6 +136,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The waits on when DreamDaemon currently has it's ports closed /// TaskCompletionSource portAssignmentTcs; + /// /// The port to assign DreamDaemon when it queries for it /// @@ -181,7 +182,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The optional time to wait before failing the public SessionController(ReattachInformation reattachInformation, IProcess process, IByondExecutableLock byondLock, IByondTopicSender byondTopicSender, IJsonTrackingContext chatJsonTrackingContext, ICommContext interopContext, IChat chat, ILogger logger, DreamDaemonSecurity? launchSecurityLevel, uint? startupTimeout) { - this.chatJsonTrackingContext = chatJsonTrackingContext; //null valid + this.chatJsonTrackingContext = chatJsonTrackingContext; // null valid this.reattachInformation = reattachInformation ?? throw new ArgumentNullException(nameof(reattachInformation)); this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); this.process = process ?? throw new ArgumentNullException(nameof(process)); @@ -219,15 +220,18 @@ namespace Tgstation.Server.Host.Components.Watchdog StartupTime = process.Startup.IsCompleted ? (TimeSpan?)(DateTimeOffset.Now - startTime) : null }; return result; - }; + } + LaunchResult = GetLaunchResult(); + + logger.LogDebug("Created session controller. Primary: {0}, CommsKey: {1}, Port: {2}", IsPrimary, reattachInformation.AccessIdentifier, Port); } /// - /// Finalize the + /// Finalizes an instance of the class. /// /// The finalizer dispose pattern is necessary so we don't accidentally leak the executable -#pragma warning disable CA1821 // Remove empty Finalizers //TODO remove this when https://github.com/dotnet/roslyn-analyzers/issues/1241 is fixed +#pragma warning disable CA1821 // Remove empty Finalizers TODO: remove this when https://github.com/dotnet/roslyn-analyzers/issues/1241 is fixed ~SessionController() => Dispose(false); #pragma warning restore CA1821 // Remove empty Finalizers @@ -238,7 +242,10 @@ namespace Tgstation.Server.Host.Components.Watchdog GC.SuppressFinalize(this); } - /// + /// + /// Implements the pattern + /// + /// If this function was NOT called by the finalizer void Dispose(bool disposing) { lock (this) @@ -252,9 +259,10 @@ namespace Tgstation.Server.Host.Components.Watchdog process.Terminate(); byondLock.Dispose(); } + process.Dispose(); interopContext.Dispose(); - Dmb?.Dispose(); //will be null when released + Dmb?.Dispose(); // will be null when released chatJsonTrackingContext.Dispose(); disposed = true; } @@ -272,6 +280,7 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// + #pragma warning disable CA1502 // TODO: Decomplexify public async Task HandleInterop(CommCommand command, CancellationToken cancellationToken) { if (command == null) @@ -305,9 +314,10 @@ namespace Tgstation.Server.Host.Components.Watchdog logger.LogDebug("Exception while decoding chat message! Exception: {0}", e); goto default; } + break; case Constants.DMCommandServerPrimed: - //currently unused, maybe in the future + // currently unused, maybe in the future break; case Constants.DMCommandEndProcess: TerminationWasRequested = true; @@ -316,7 +326,7 @@ namespace Tgstation.Server.Host.Components.Watchdog case Constants.DMCommandNewPort: lock (this) { - if (!query.TryGetValue(Constants.DMParameterData, out var stringPort) || !UInt16.TryParse(stringPort, out var currentPort)) + if (!query.TryGetValue(Constants.DMParameterData, out var stringPortObject) || !UInt16.TryParse(stringPortObject as string, out var currentPort)) { /////UHHHH logger.LogWarning("DreamDaemon sent new port command without providing it's own!"); @@ -325,18 +335,17 @@ namespace Tgstation.Server.Host.Components.Watchdog } if (!nextPort.HasValue) - //not ready yet, so what we'll do is accept the random port DD opened on for now and change it later when we decide to - reattachInformation.Port = currentPort; + reattachInformation.Port = currentPort; // not ready yet, so what we'll do is accept the random port DD opened on for now and change it later when we decide to else { - //nextPort is ready, tell DD to switch to that - //if it fails it'll kill itself + // nextPort is ready, tell DD to switch to that + // if it fails it'll kill itself content = new Dictionary { { Constants.DMParameterData, nextPort.Value } }; reattachInformation.Port = nextPort.Value; overrideResponsePort = currentPort; nextPort = null; - //we'll also get here from SetPort so complete that task + // we'll also get here from SetPort so complete that task var tmpTcs = portAssignmentTcs; portAssignmentTcs = null; if (tmpTcs != null) @@ -345,6 +354,7 @@ namespace Tgstation.Server.Host.Components.Watchdog portClosedForReboot = false; } + break; case Constants.DMCommandApiValidate: if (!launchSecurityLevel.HasValue) @@ -354,7 +364,8 @@ namespace Tgstation.Server.Host.Components.Watchdog content = new ErrorMessage { Message = "Invalid API validation request!" }; break; } - if (!query.TryGetValue(Constants.DMParameterData, out var stringMinimumSecurityLevel) || !Enum.TryParse(stringMinimumSecurityLevel, out var minimumSecurityLevel)) + + if (!query.TryGetValue(Constants.DMParameterData, out var stringMinimumSecurityLevelObject) || !Enum.TryParse(stringMinimumSecurityLevelObject as string, out var minimumSecurityLevel)) apiValidationStatus = ApiValidationStatus.BadValidationRequest; else switch (minimumSecurityLevel) @@ -371,6 +382,7 @@ namespace Tgstation.Server.Host.Components.Watchdog default: throw new InvalidOperationException("Enum.TryParse failed to validate the DreamDaemonSecurity range!"); } + break; case Constants.DMCommandWorldReboot: if (ClosePortOnReboot) @@ -379,6 +391,7 @@ namespace Tgstation.Server.Host.Components.Watchdog content = new Dictionary { { Constants.DMParameterData, 0 } }; portClosedForReboot = true; } + var oldTcs = rebootTcs; rebootTcs = new TaskCompletionSource(); postRespond = () => oldTcs.SetResult(null); @@ -399,6 +412,7 @@ namespace Tgstation.Server.Host.Components.Watchdog postRespond?.Invoke(); } + #pragma warning restore CA1502 /// /// Throws an if has been called @@ -416,7 +430,8 @@ namespace Tgstation.Server.Host.Components.Watchdog public ReattachInformation Release() { CheckDisposed(); - //we still don't want to dispose the dmb yet, even though we're keeping it alive + + // we still don't want to dispose the dmb yet, even though we're keeping it alive var tmpProvider = reattachInformation.Dmb; reattachInformation.Dmb = null; released = true; @@ -439,8 +454,7 @@ namespace Tgstation.Server.Host.Components.Watchdog byondTopicSender.SanitizeString(Constants.DMInteropAccessIdentifier), byondTopicSender.SanitizeString(reattachInformation.AccessIdentifier), byondTopicSender.SanitizeString(Constants.DMParameterCommand), - //intentionally don't sanitize command, that's up to the caller - command); + command); // intentionally don't sanitize command, that's up to the caller var targetPort = overridePort ?? reattachInformation.Port; logger.LogTrace("Export to :{0}. Query: {1}", targetPort, commandString); @@ -479,6 +493,7 @@ namespace Tgstation.Server.Host.Components.Watchdog return false; } + reattachInformation.Port = port; return true; } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index 4715f6443e..cd1e7c082d 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -5,7 +5,6 @@ using Newtonsoft.Json.Serialization; using System; using System.Globalization; using System.Linq; -using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -128,6 +127,7 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// + #pragma warning disable CA1506 // TODO: Decomplexify public async Task LaunchNew(DreamDaemonLaunchParameters launchParameters, IDmbProvider dmbProvider, IByondExecutableLock currentByondLock, bool primaryPort, bool primaryDirectory, bool apiValidate, CancellationToken cancellationToken) { var portToUse = primaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort; @@ -138,12 +138,12 @@ namespace Tgstation.Server.Host.Components.Watchdog const string JsonPostfix = "tgs.json"; var basePath = primaryDirectory ? dmbProvider.PrimaryDirectory : dmbProvider.SecondaryDirectory; - //delete all previous tgs json files - var files = await ioManager.GetFilesWithExtension(basePath, JsonPostfix, cancellationToken).ConfigureAwait(false); + // delete all previous tgs json files + var files = await ioManager.GetFilesWithExtension(basePath, JsonPostfix, cancellationToken).ConfigureAwait(false); await Task.WhenAll(files.Select(x => ioManager.DeleteFile(x, cancellationToken))).ConfigureAwait(false); - //i changed this back from guids, hopefully i don't regret that + // i changed this back from guids, hopefully i don't regret that string JsonFile(string name) => String.Format(CultureInfo.InvariantCulture, "{0}.{1}", name, JsonPostfix); var securityLevelToUse = launchParameters.SecurityLevel.Value; @@ -162,7 +162,7 @@ namespace Tgstation.Server.Host.Components.Watchdog throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid DreamDaemonSecurity value: {0}", dmbProvider.CompileJob.MinimumSecurityLevel)); } - //setup interop files + // setup interop files var interopInfo = new JsonFile { AccessIdentifier = accessIdentifier, @@ -197,19 +197,19 @@ namespace Tgstation.Server.Host.Components.Watchdog var chatJsonTrackingContext = await chatJsonTrackingTask.ConfigureAwait(false); try { - //get the byond lock + // get the byond lock var byondLock = currentByondLock ?? await byond.UseExecutables(Version.Parse(dmbProvider.CompileJob.ByondVersion), cancellationToken).ConfigureAwait(false); try { - //create interop context + // create interop context var context = new CommContext(ioManager, loggerFactory.CreateLogger(), basePath, interopInfo.ServerCommandsJson); try { - //set command line options - //more sanitization here cause it uses the same scheme + // set command line options + // more sanitization here cause it uses the same scheme var parameters = String.Format(CultureInfo.InvariantCulture, "{2}={0}&{3}={1}", byondTopicSender.SanitizeString(application.Version.ToString()), byondTopicSender.SanitizeString(interopJsonFile), byondTopicSender.SanitizeString(Constants.DMParamHostVersion), byondTopicSender.SanitizeString(Constants.DMParamInfoJson)); - //important to run on all ports to allow port changing + // important to run on all ports to allow port changing var arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} -ports 1-65535 {2}-close -{3} -verbose -public -params \"{4}\"", dmbProvider.DmbName, primaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, @@ -217,15 +217,16 @@ namespace Tgstation.Server.Host.Components.Watchdog SecurityWord(securityLevelToUse), parameters); - //See #719 + // See https://github.com/tgstation/tgstation-server/issues/719 var noShellExecute = !platformIdentifier.IsWindows; - //launch dd + + // launch dd var process = processExecutor.LaunchProcess(byondLock.DreamDaemonPath, basePath, arguments, noShellExecute: noShellExecute); try { networkPromptReaper.RegisterProcess(process); - //return the session controller for it + // return the session controller for it var result = new SessionController(new ReattachInformation { AccessIdentifier = accessIdentifier, @@ -238,7 +239,7 @@ namespace Tgstation.Server.Host.Components.Watchdog ServerCommandsJson = interopInfo.ServerCommandsJson, }, process, byondLock, byondTopicSender, chatJsonTrackingContext, context, chat, loggerFactory.CreateLogger(), launchParameters.SecurityLevel, launchParameters.StartupTimeout); - //writeback launch parameter's fixed security level + // writeback launch parameter's fixed security level launchParameters.SecurityLevel = securityLevelToUse; return result; @@ -268,6 +269,7 @@ namespace Tgstation.Server.Host.Components.Watchdog throw; } } + #pragma warning restore CA1506 /// public async Task Reattach(ReattachInformation reattachInformation, CancellationToken cancellationToken) @@ -317,6 +319,7 @@ namespace Tgstation.Server.Host.Components.Watchdog if (result == null) chatJsonTrackingContext.Dispose(); } + return result; } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 0302b24fce..4a91fc57e4 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -5,6 +5,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.Linq; using System.Text; @@ -20,6 +21,7 @@ using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components.Watchdog { /// + #pragma warning disable CA1506 // TODO: Decomplexify sealed class Watchdog : IWatchdog, ICustomCommandHandler, IRestartHandler { /// @@ -134,6 +136,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// Server designation alpha /// ISessionController alphaServer; + /// /// Server designation bravo /// @@ -179,7 +182,7 @@ namespace Tgstation.Server.Host.Components.Watchdog if (serverControl == null) throw new ArgumentNullException(nameof(serverControl)); - + chat.RegisterCommandHandler(this); AlphaIsActive = true; @@ -239,7 +242,7 @@ namespace Tgstation.Server.Host.Components.Watchdog return; } - //merely set the reboot state + // merely set the reboot state var toKill = AlphaIsActive ? alphaServer : bravoServer; var other = AlphaIsActive ? bravoServer : alphaServer; if (toKill != null) @@ -253,30 +256,32 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The current . Will be modified upon retrn /// The for the operation /// A representing the running operation + #pragma warning disable CA1502 // TODO: Decomplexify async Task HandlerMonitorWakeup(MonitorActivationReason activationReason, MonitorState monitorState, CancellationToken cancellationToken) { logger.LogDebug("Monitor activation. Reason: {0}", activationReason); - //this is where the bulk of the watchdog handling code lives and is fraught with lambdas, sorry not sorry - //I'll do my best to walk you through it + // this is where the bulk of the watchdog handling code lives and is fraught with lambdas, sorry not sorry + // I'll do my best to walk you through it - //returns true if the inactive server can't be used immediately - //also sets monitor to restart if the above holds + // returns true if the inactive server can't be used immediately + // also sets monitor to restart if the above holds bool FullRestartDeadInactive() { if (monitorState.RebootingInactiveServer || monitorState.InactiveServerCritFail) { logger.LogInformation("Inactive server is {0}! Restarting monitor...", monitorState.InactiveServerCritFail ? "critically failed" : "still rebooting"); - monitorState.NextAction = MonitorAction.Restart; //will dispose server + monitorState.NextAction = MonitorAction.Restart; // will dispose server return true; } - return false; - }; - //trys to set inactive server's port to the public game port - //doesn't handle closing active server's port - //returns true on success and swaps inactiveserver and activeserver also sets LastLaunchParameters to ActiveLaunchParameters - //on failure, sets monitor to restart + return false; + } + + // trys to set inactive server's port to the public game port + // doesn't handle closing active server's port + // returns true on success and swaps inactiveserver and activeserver also sets LastLaunchParameters to ActiveLaunchParameters + // on failure, sets monitor to restart async Task MakeInactiveActive() { logger.LogDebug("Setting inactive server to port {0}...", ActiveLaunchParameters.PrimaryPort.Value); @@ -285,11 +290,11 @@ namespace Tgstation.Server.Host.Components.Watchdog if (!result) { logger.LogWarning("Failed to activate inactive server! Restarting monitor..."); - monitorState.NextAction = MonitorAction.Restart; //will dispose server + monitorState.NextAction = MonitorAction.Restart; // will dispose server return false; } - //inactive server should always be using active launch parameters + // inactive server should always be using active launch parameters LastLaunchParameters = ActiveLaunchParameters; var tmp = monitorState.ActiveServer; @@ -307,7 +312,7 @@ namespace Tgstation.Server.Host.Components.Watchdog async Task UpdateAndRestartInactiveServer(bool breakAfter) { activeParametersUpdated = new TaskCompletionSource(); - monitorState.InactiveServer.Dispose(); //kill or recycle it + monitorState.InactiveServer.Dispose(); // kill or recycle it var desiredNextAction = breakAfter ? MonitorAction.Break : MonitorAction.Continue; monitorState.NextAction = desiredNextAction; @@ -325,17 +330,17 @@ namespace Tgstation.Server.Host.Components.Watchdog catch (Exception e) { logger.LogError("Error occurred while recreating server! Attempting backup strategy of running DMB of running server! Exception: {0}", e.ToString()); - //ahh jeez, what do we do here? - //this is our fault, so it should never happen but - //idk maybe a database error while handling the newest dmb? - //either way try to start it using the active server's dmb as a backup + + // ahh jeez, what do we do here? + // this is our fault, so it should never happen but + // idk maybe a database error while handling the newest dmb? + // either way try to start it using the active server's dmb as a backup try { var dmbBackup = await dmbFactory.FromCompileJob(monitorState.ActiveServer.Dmb.CompileJob, cancellationToken).ConfigureAwait(false); - if (dmbBackup == null) //NANI!? - //just give up, if THAT compile job is failing then the ActiveServer is gonna crash soon too or already has - throw new JobException("Creating backup DMB provider failed!"); + if (dmbBackup == null) // NANI!? + throw new JobException("Creating backup DMB provider failed!"); // just give up, if THAT compile job is failing then the ActiveServer is gonna crash soon too or already has monitorState.InactiveServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbBackup, null, false, !monitorState.ActiveServer.IsPrimary, false, cancellationToken).ConfigureAwait(false); monitorState.InactiveServer.SetHighPriority(); @@ -347,7 +352,7 @@ namespace Tgstation.Server.Host.Components.Watchdog } catch (Exception e2) { - //fuuuuucckkk + // fuuuuucckkk logger.LogError("Backup strategy failed! Monitor will restart when active server reboots! Exception: {0}", e2.ToString()); monitorState.InactiveServerCritFail = true; await chat.SendWatchdogMessage("Attempted reboot of inactive server failed. Watchdog will reset when active server fails or exits", cancellationToken).ConfigureAwait(false); @@ -361,13 +366,13 @@ namespace Tgstation.Server.Host.Components.Watchdog string ExitWord(ISessionController controller) => controller.TerminationWasRequested ? "exited" : "crashed"; - //reason handling + // reason handling switch (activationReason) { case MonitorActivationReason.ActiveServerCrashed: if (monitorState.ActiveServer.RebootState == Components.Watchdog.RebootState.Shutdown) { - //the time for graceful shutdown is now + // the time for graceful shutdown is now await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Active server {0}! Exiting due to graceful termination request...", ExitWord(monitorState.ActiveServer)), cancellationToken).ConfigureAwait(false); DisposeAndNullControllers(); monitorState.NextAction = MonitorAction.Exit; @@ -376,146 +381,144 @@ namespace Tgstation.Server.Host.Components.Watchdog if (FullRestartDeadInactive()) { - //tell chat about it and go ahead + // tell chat about it and go ahead await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Active server {0}! Inactive server unable to online!", ExitWord(monitorState.ActiveServer)), cancellationToken).ConfigureAwait(false); - //we've already been set to restart + + // we've already been set to restart break; } - //tell chat about it + // tell chat about it await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Active server {0}! Onlining inactive server...", ExitWord(monitorState.ActiveServer)), cancellationToken).ConfigureAwait(false); - //try to active the inactive server + // try to activate the inactive server if (!await MakeInactiveActive().ConfigureAwait(false)) - //failing that, we've already been set to restart - break; + break; // failing that, we've already been set to restart - //bring up another inactive server + // bring up another inactive server await UpdateAndRestartInactiveServer(true).ConfigureAwait(false); break; case MonitorActivationReason.InactiveServerCrashed: - //just announce and try to bring it back + // just announce and try to bring it back await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Inactive server {0}! Rebooting...", ExitWord(monitorState.InactiveServer)), cancellationToken).ConfigureAwait(false); await UpdateAndRestartInactiveServer(false).ConfigureAwait(false); break; case MonitorActivationReason.ActiveServerRebooted: - //ideal goal: active server just closed its port - //tell inactive server to open it's port and that's now the active server + // ideal goal: active server just closed its port + // tell inactive server to open it's port and that's now the active server var rebootState = monitorState.ActiveServer.RebootState; - monitorState.ActiveServer.ResetRebootState(); //the DMAPI has already done this internally + monitorState.ActiveServer.ResetRebootState(); // the DMAPI has already done this internally if (FullRestartDeadInactive() && rebootState != Components.Watchdog.RebootState.Shutdown) - //full restart if the inactive server is being fucky - break; + break; // full restart if the inactive server is being fucky - //what matters here is the RebootState + // what matters here is the RebootState var restartOnceSwapped = false; switch (rebootState) { case Components.Watchdog.RebootState.Normal: - //life as normal + // life as normal break; case Components.Watchdog.RebootState.Restart: - //reboot the current active server once the inactive one activates + // reboot the current active server once the inactive one activates restartOnceSwapped = true; break; case Components.Watchdog.RebootState.Shutdown: - //graceful shutdown time + // graceful shutdown time await chat.SendWatchdogMessage("Active server rebooted! Exiting due to graceful termination request...", cancellationToken).ConfigureAwait(false); DisposeAndNullControllers(); monitorState.NextAction = MonitorAction.Exit; return; + default: + Trace.Assert(false, String.Format(CultureInfo.InvariantCulture, "Invalid RebootState: {0}!", rebootState)); + break; } - //are both servers now running the same CompileJob? + // are both servers now running the same CompileJob? var sameCompileJob = monitorState.InactiveServer.Dmb.CompileJob.Id == monitorState.ActiveServer.Dmb.CompileJob.Id; - + if (!sameCompileJob || ActiveLaunchParameters != LastLaunchParameters) - //need a new launch to update either settings or compile job - restartOnceSwapped = true; + restartOnceSwapped = true; // need a new launch to update either settings or compile job if (restartOnceSwapped) - //we need to manually restart active server - //just kill it here, easier that way + /* + * we need to manually restart active server + * just kill it here, easier that way + */ monitorState.ActiveServer.Dispose(); var activeServerStillHasPortOpen = !restartOnceSwapped && !monitorState.ActiveServer.ClosePortOnReboot; if (activeServerStillHasPortOpen) - //we didn't want active server to swap for some reason and it still has it's port open - //just continue as normal + /* we didn't want active server to swap for some reason and it still has it's port open + * just continue as normal + */ break; - + if (!await MakeInactiveActive().ConfigureAwait(false)) - //monitor will restart - break; + break; // monitor will restart - //servers now swapped - - //enable this now - monitorState.ActiveServer.ClosePortOnReboot = true; + // servers now swapped + // enable this now if inactive server is not still valid + monitorState.ActiveServer.ClosePortOnReboot = restartOnceSwapped; if (!restartOnceSwapped) - { - //inactive server still valid - - //disable this - monitorState.InactiveServer.ClosePortOnReboot = false; - - //now try to reopen it on the private port - //failing that, just reboot it + /* + * now try to reopen it on the private port + * failing that, just reboot it + */ restartOnceSwapped = !await monitorState.InactiveServer.SetPort(ActiveLaunchParameters.SecondaryPort.Value, cancellationToken).ConfigureAwait(false); - } - //break either way because any issues past this point would be solved by the reboot - if (restartOnceSwapped) - //for one reason or another - //update and reboot - await UpdateAndRestartInactiveServer(true).ConfigureAwait(false); + // break either way because any issues past this point would be solved by the reboot + if (restartOnceSwapped) // for one reason or another + await UpdateAndRestartInactiveServer(true).ConfigureAwait(false); // update and reboot else - //only skip checking inactive server rebooted, it's guaranteed InactiveServerStartup complete wouldn't fire this iteration - monitorState.NextAction = MonitorAction.Skip; + monitorState.NextAction = MonitorAction.Skip; // only skip checking inactive server rebooted, it's guaranteed InactiveServerStartup complete wouldn't fire this iteration break; case MonitorActivationReason.InactiveServerRebooted: - //just don't let the active server close it's port if the inactive server isn't ready + // just don't let the active server close it's port if the inactive server isn't ready monitorState.RebootingInactiveServer = true; monitorState.InactiveServer.ResetRebootState(); monitorState.ActiveServer.ClosePortOnReboot = false; monitorState.NextAction = MonitorAction.Continue; break; case MonitorActivationReason.InactiveServerStartupComplete: - //opposite of above case + // opposite of above case monitorState.RebootingInactiveServer = false; monitorState.ActiveServer.ClosePortOnReboot = true; monitorState.NextAction = MonitorAction.Continue; break; case MonitorActivationReason.NewDmbAvailable: case MonitorActivationReason.ActiveLaunchParametersUpdated: - //just reload the inactive server and wait for a swap to apply the changes + // just reload the inactive server and wait for a swap to apply the changes await UpdateAndRestartInactiveServer(true).ConfigureAwait(false); break; + default: + Trace.Assert(false, String.Format(CultureInfo.InvariantCulture, "Invalid monitor activation reason: {0}!", activationReason)); + break; } } + #pragma warning restore CA1502 /// /// The loop that watches the watchdog /// /// The for the operation /// A representing the running operation +#pragma warning disable CA1502 // TODO: Decomplexify async Task MonitorLifetimes(CancellationToken cancellationToken) { logger.LogTrace("Entered MonitorLifetimes"); - //this function is responsible for calling HandlerMonitorWakeup when necessary and manitaining the MonitorState - + // this function is responsible for calling HandlerMonitorWakeup when necessary and manitaining the MonitorState var iteration = 1; for (var monitorState = new MonitorState(); monitorState.NextAction != MonitorAction.Exit; ++iteration) { - //always start out with continue + // always start out with continue monitorState.NextAction = MonitorAction.Continue; - //dump some info to the logs + // dump some info to the logs logger.LogDebug("Iteration {0} of monitor loop", iteration); try { @@ -523,12 +526,11 @@ namespace Tgstation.Server.Host.Components.Watchdog logger.LogDebug("Alpha is the active server"); else logger.LogDebug("Bravo is the active server"); - - + if (monitorState.RebootingInactiveServer) logger.LogDebug("Inactive server is rebooting"); - //update the monitor state with the inactive/active servers + // update the monitor state with the inactive/active servers monitorState.ActiveServer = AlphaIsActive ? alphaServer : bravoServer; monitorState.InactiveServer = AlphaIsActive ? bravoServer : alphaServer; @@ -540,7 +542,7 @@ namespace Tgstation.Server.Host.Components.Watchdog logger.LogDebug("Active server Compile Job ID: {0}", monitorState.ActiveServer.Dmb.CompileJob.Id); logger.LogDebug("Inactive server Compile Job ID: {0}", monitorState.InactiveServer.Dmb.CompileJob.Id); - //load the activation tasks into local variables + // load the activation tasks into local variables Task activeServerLifetime = monitorState.ActiveServer.Lifetime; Task inactiveServerLifetime = monitorState.InactiveServer.Lifetime; var activeServerReboot = monitorState.ActiveServer.OnReboot; @@ -549,14 +551,15 @@ namespace Tgstation.Server.Host.Components.Watchdog Task activeLaunchParametersChanged = activeParametersUpdated.Task; var newDmbAvailable = dmbFactory.OnNewerDmb; - //cancel waiting if requested + // cancel waiting if requested var cancelTcs = new TaskCompletionSource(); using (cancellationToken.Register(() => cancelTcs.SetCanceled())) { var toWaitOn = Task.WhenAny(activeServerLifetime, inactiveServerLifetime, activeServerReboot, inactiveServerReboot, newDmbAvailable, cancelTcs.Task, activeLaunchParametersChanged); if (monitorState.RebootingInactiveServer) toWaitOn = Task.WhenAny(toWaitOn, inactiveServerStartup); - //wait for something to happen + + // wait for something to happen await toWaitOn.ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); } @@ -564,14 +567,13 @@ namespace Tgstation.Server.Host.Components.Watchdog var chatTask = Task.CompletedTask; using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { - //always run HandleMonitorWakeup from the context of the semaphore lock - //multiple things may have happened, handle them one at a time + // always run HandleMonitorWakeup from the context of the semaphore lock + // multiple things may have happened, handle them one at a time for (var moreActivationsToProcess = true; moreActivationsToProcess && (monitorState.NextAction == MonitorAction.Continue || monitorState.NextAction == MonitorAction.Skip);) { - MonitorActivationReason activationReason = default; //this will always be assigned before being used - - //process the tasks in this order and call HandlerMonitorWakup for each + MonitorActivationReason activationReason = default; // this will always be assigned before being used + // process the tasks in this order and call HandlerMonitorWakup for each bool CheckActivationReason(ref Task task, MonitorActivationReason testActivationReason) { var taskCompleted = task?.IsCompleted == true; @@ -583,8 +585,9 @@ namespace Tgstation.Server.Host.Components.Watchdog activationReason = testActivationReason; return true; } - return false; - }; + + return false; + } if (CheckActivationReason(ref activeServerLifetime, MonitorActivationReason.ActiveServerCrashed) || CheckActivationReason(ref inactiveServerLifetime, MonitorActivationReason.InactiveServerCrashed) @@ -598,12 +601,12 @@ namespace Tgstation.Server.Host.Components.Watchdog moreActivationsToProcess = false; } - //writeback alphaServer and bravoServer from monitor state in case they changesd + // writeback alphaServer and bravoServer from monitor state in case they changesd alphaServer = AlphaIsActive ? monitorState.ActiveServer : monitorState.InactiveServer; bravoServer = !AlphaIsActive ? monitorState.ActiveServer : monitorState.InactiveServer; } - //full reboot required + // full reboot required if (monitorState.NextAction == MonitorAction.Restart) { logger.LogDebug("Next state action is to restart"); @@ -616,12 +619,12 @@ namespace Tgstation.Server.Host.Components.Watchdog using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) try { - //use LaunchImplNoLock without announcements or restarting the monitor + // use LaunchImplNoLock without announcements or restarting the monitor await LaunchImplNoLock(false, false, null, cancellationToken).ConfigureAwait(false); if (Running) { logger.LogDebug("Relaunch successful, resetting monitor state..."); - monitorState = new MonitorState(); //clean the slate and continue + monitorState = new MonitorState(); // clean the slate and continue } } catch (OperationCanceledException) @@ -640,7 +643,7 @@ namespace Tgstation.Server.Host.Components.Watchdog logger.LogWarning("Failed to automatically restart the watchdog! Attempt: {0}", retryAttempts); else logger.LogWarning("Failed to automatically restart the watchdog! Attempt: {0}, Exception: {1}", retryAttempts, launchException); - var retryDelay = Math.Min(Math.Pow(2, retryAttempts), 3600); //max of one hour, increasing by a power of 2 each time + var retryDelay = Math.Min(Math.Pow(2, retryAttempts), 3600); // max of one hour, increasing by a power of 2 each time chatTask = chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Failed to restart watchdog (Attempt: {0}), retrying in {1} seconds...", retryAttempts, retryDelay), cancellationToken); await Task.WhenAll(asyncDelayer.Delay(TimeSpan.FromSeconds(retryDelay), cancellationToken), chatTask).ConfigureAwait(false); } @@ -654,13 +657,15 @@ namespace Tgstation.Server.Host.Components.Watchdog } catch (Exception e) { - //really, this should NEVER happen + // really, this should NEVER happen logger.LogError("Monitor crashed! Iteration: {0}, State: {1}, Exception: {2}", iteration, JsonConvert.SerializeObject(monitorState), e); await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Monitor crashed, this should NEVER happen! Please report this, full details in logs! Restarting monitor... Error: {0}", e.Message), cancellationToken).ConfigureAwait(false); } } + logger.LogTrace("Monitor exiting..."); } + #pragma warning restore CA1502 /// /// Stops . Doesn't kill the servers @@ -678,7 +683,6 @@ namespace Tgstation.Server.Host.Components.Watchdog return true; } - /// public async Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken) { @@ -688,8 +692,7 @@ namespace Tgstation.Server.Host.Components.Watchdog return; ActiveLaunchParameters = launchParameters; if (Running) - //queue an update - activeParametersUpdated.TrySetResult(null); + activeParametersUpdated.TrySetResult(null); // queue an update } } @@ -701,6 +704,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// to use, if any /// The for the operation /// A representing the running operation + #pragma warning disable CA1502 // TODO: Decomplexify async Task LaunchImplNoLock(bool startMonitor, bool announce, WatchdogReattachInformation reattachInfo, CancellationToken cancellationToken) { logger.LogTrace("Begin LaunchNoLock"); @@ -708,67 +712,68 @@ namespace Tgstation.Server.Host.Components.Watchdog if (Running) throw new JobException("Watchdog already running!"); + // this is necessary, the monitor could be in it's sleep loop trying to restart, if so cancel THAT monitor and start our own with blackjack and hookers Task chatTask; - //this is necessary, the monitor could be in it's sleep loop trying to restart, if so cancel THAT monitor and start our own with blackjack and hookers if (startMonitor && await StopMonitor().ConfigureAwait(false)) chatTask = chat.SendWatchdogMessage("Automatic retry sequence cancelled by manual launch. Restarting...", cancellationToken); else if (announce) - //simple announce - chatTask = chat.SendWatchdogMessage(reattachInfo == null ? "Starting..." : "Reattaching...", cancellationToken); + chatTask = chat.SendWatchdogMessage(reattachInfo == null ? "Starting..." : "Reattaching...", cancellationToken); // simple announce else - //no announce - chatTask = Task.CompletedTask; - - //since neither server is running, this is safe to do + chatTask = Task.CompletedTask; // no announce + + // since neither server is running, this is safe to do LastLaunchParameters = ActiveLaunchParameters; - //for when we call ourself and want to not catch thrown exceptions + // for when we call ourself and want to not catch thrown exceptions var ignoreNestedException = false; try { - //good ole sanity, should never fucking trigger but i don't trust myself even though I should + // good ole sanity, should never fucking trigger but i don't trust myself even though I should + // TODO: Unit test this instead? if (alphaServer != null || bravoServer != null) throw new InvalidOperationException("Entered LaunchNoLock with one or more of the servers not being null!"); - - //don't need a new dmb if reattaching + + // don't need a new dmb if reattaching var doesntNeedNewDmb = reattachInfo?.Alpha != null && reattachInfo?.Bravo != null; var dmbToUse = doesntNeedNewDmb ? null : dmbFactory.LockNextDmb(2); - //if this try catches something, both servers are killed + // if this try catches something, both servers are killed try { - //start the alpha server task, either by launch a new process or attaching to an existing one - //The tasks returned are mainly for writing interop files to the directories among other things and should generally never fail - //The tasks pertaining to server startup times are in the ISessionControllers + // start the alpha server task, either by launch a new process or attaching to an existing one + // The tasks returned are mainly for writing interop files to the directories among other things and should generally never fail + // The tasks pertaining to server startup times are in the ISessionControllers Task alphaServerTask; if (!doesntNeedNewDmb) alphaServerTask = sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, null, true, true, false, cancellationToken); else alphaServerTask = sessionControllerFactory.Reattach(reattachInfo.Alpha, cancellationToken); - //retrieve the session controller + // retrieve the session controller var startTime = DateTimeOffset.Now; alphaServer = await alphaServerTask.ConfigureAwait(false); - //failed reattaches will return null + + // failed reattaches will return null alphaServer?.SetHighPriority(); - //extra delay for total ordering + // extra delay for total ordering var now = DateTimeOffset.Now; var delay = now - startTime; - //definitely not if reattaching though + // definitely not if reattaching though if (reattachInfo == null && delay.TotalSeconds < AlphaBravoStartupSeperationInterval) await asyncDelayer.Delay(startTime.AddSeconds(AlphaBravoStartupSeperationInterval) - now, cancellationToken).ConfigureAwait(false); - //now bring bravo up + // now bring bravo up if (!doesntNeedNewDmb) bravoServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, null, false, false, false, cancellationToken).ConfigureAwait(false); else bravoServer = await sessionControllerFactory.Reattach(reattachInfo.Bravo, cancellationToken).ConfigureAwait(false); - //failed reattaches will return null + + // failed reattaches will return null bravoServer?.SetHighPriority(); - //possiblity of null servers due to failed reattaches + // possiblity of null servers due to failed reattaches if (alphaServer == null || bravoServer == null) { await chatTask.ConfigureAwait(false); @@ -777,7 +782,7 @@ namespace Tgstation.Server.Host.Components.Watchdog || (alphaServer == null && reattachInfo.AlphaIsActive) || (bravoServer == null && !reattachInfo.AlphaIsActive)) { - //we lost the active server, just restart entirely + // we lost the active server, just restart entirely DisposeAndNullControllers(); const string FailReattachMessage = "Unable to properly reattach to active server! Restarting..."; logger.LogWarning(FailReattachMessage); @@ -788,7 +793,7 @@ namespace Tgstation.Server.Host.Components.Watchdog return; } - //we still have the active server but the other one is dead to us, hand it off to the monitor to restart + // we still have the active server but the other one is dead to us, hand it off to the monitor to restart const string InactiveReattachFailureMessage = "Unable to reattach to inactive server. Leaving for monitor to reboot..."; chatTask = chat.SendWatchdogMessage(InactiveReattachFailureMessage, cancellationToken); logger.LogWarning(InactiveReattachFailureMessage); @@ -799,13 +804,12 @@ namespace Tgstation.Server.Host.Components.Watchdog alphaServer = sessionControllerFactory.CreateDeadSession(reattachInfo.Alpha.Dmb); } - //throws a JobException if something went wrong with a launch - //Dead sessions won't trigger this + // throws a JobException if something went wrong with a launch + // Dead sessions won't trigger this async Task CheckLaunch(ISessionController controller, string serverName) { var launch = await controller.LaunchResult.ConfigureAwait(false); - if (launch.ExitCode.HasValue) - //you killed us ray... + if (launch.ExitCode.HasValue) // you killed us ray... throw new JobException(String.Format(CultureInfo.InvariantCulture, "{1} server failed to start: {0}", launch.ToString(), serverName)); if (!launch.StartupTime.HasValue) throw new JobException(String.Format(CultureInfo.InvariantCulture, "{1} server timed out on startup: {0}s", launch.ToString(), ActiveLaunchParameters.StartupTimeout.Value)); @@ -814,10 +818,11 @@ namespace Tgstation.Server.Host.Components.Watchdog var alphaLrt = CheckLaunch(alphaServer, "Alpha"); var bravoLrt = CheckLaunch(bravoServer, "Bravo"); - //this task completes when both serers have finished booting + + // this task completes when both serers have finished booting var allTask = Task.WhenAll(alphaLrt, bravoLrt); - //don't forget about the cancellationToken + // don't forget about the cancellationToken var cancelTcs = new TaskCompletionSource(); using (cancellationToken.Register(() => cancelTcs.SetCanceled())) await Task.WhenAny(allTask, cancelTcs.Task).ConfigureAwait(false); @@ -825,7 +830,7 @@ namespace Tgstation.Server.Host.Components.Watchdog await allTask.ConfigureAwait(false); - //both servers are now running, alpha is the active server(unless reattach), huzzah + // both servers are now running, alpha is the active server(unless reattach), huzzah AlphaIsActive = reattachInfo?.AlphaIsActive ?? true; var activeServer = AlphaIsActive ? alphaServer : bravoServer; @@ -845,34 +850,32 @@ namespace Tgstation.Server.Host.Components.Watchdog { if (dmbToUse != null) { - //we locked 2 dmbs + // we locked 2 dmbs if (bravoServer == null) { - //bravo didn't get control of his + // bravo didn't get control of his dmbToUse.Dispose(); if (alphaServer == null) - //alpha didn't get control of his - dmbToUse.Dispose(); + dmbToUse.Dispose(); // alpha didn't get control of his } } - else if (doesntNeedNewDmb) - //we have reattachInfo + else if (doesntNeedNewDmb) // we have reattachInfo if (bravoServer == null) { - //bravo didn't get control of his + // bravo didn't get control of his reattachInfo.Bravo?.Dmb.Dispose(); if (alphaServer == null) - //alpha didn't get control of his - reattachInfo.Alpha?.Dmb.Dispose(); + reattachInfo.Alpha?.Dmb.Dispose(); // alpha didn't get control of his } - //kill the controllers + + // kill the controllers DisposeAndNullControllers(); throw; } } catch (Exception e) { - //don't try to send chat tasks or warning logs if were suppressing exceptions or cancelled + // don't try to send chat tasks or warning logs if were suppressing exceptions or cancelled if (!ignoreNestedException && !cancellationToken.IsCancellationRequested) { var originalChatTask = chatTask; @@ -881,14 +884,16 @@ namespace Tgstation.Server.Host.Components.Watchdog await originalChatTask.ConfigureAwait(false); await chat.SendWatchdogMessage("Startup failed!", cancellationToken).ConfigureAwait(false); } + chatTask = ChainChatTaskWithErrorMessage(); logger.LogWarning("Failed to start watchdog: {0}", e.ToString()); } + throw; } finally { - //finish the chat task that's in flight + // finish the chat task that's in flight try { await chatTask.ConfigureAwait(false); @@ -896,6 +901,7 @@ namespace Tgstation.Server.Host.Components.Watchdog catch (OperationCanceledException) { } } } + #pragma warning restore CA1502 /// public async Task Launch(CancellationToken cancellationToken) @@ -936,6 +942,7 @@ namespace Tgstation.Server.Host.Components.Watchdog await LaunchImplNoLock(true, !Running, null, cancellationToken).ConfigureAwait(false); await chatTask.ConfigureAwait(false); } + var toReboot = AlphaIsActive ? alphaServer : bravoServer; if (toReboot != null) { @@ -1001,6 +1008,7 @@ namespace Tgstation.Server.Host.Components.Watchdog reattachInformation.Bravo = bravoServer?.Release(); await reattachInfoHandler.Save(reattachInformation, cancellationToken).ConfigureAwait(false); } + await Terminate(false, cancellationToken).ConfigureAwait(false); } @@ -1059,7 +1067,7 @@ namespace Tgstation.Server.Host.Components.Watchdog var commandObject = new ChatCommand { Command = commandName, - Parameters = arguments, + Params = arguments, User = sender }; diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WindowsNetworkPromptReaper.cs b/src/Tgstation.Server.Host/Components/Watchdog/WindowsNetworkPromptReaper.cs index 719eb9f4f8..737d053e09 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WindowsNetworkPromptReaper.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WindowsNetworkPromptReaper.cs @@ -19,7 +19,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// Number of times to send the button click message. Should be at least 2 or it may fail to focus the window /// const int SendMessageCount = 5; - + /// /// Check for prompts each time this amount of milliseconds pass /// @@ -41,7 +41,7 @@ namespace Tgstation.Server.Host.Components.Watchdog readonly CancellationTokenSource cancellationTokenSource; /// - /// The list of s registered + /// The list of s registered /// readonly List registeredProcesses; @@ -77,6 +77,7 @@ namespace Tgstation.Server.Host.Components.Watchdog { gcChildhandlesList.Free(); } + return childHandles; } @@ -117,12 +118,12 @@ namespace Tgstation.Server.Host.Components.Watchdog if (window == IntPtr.Zero) continue; - //found a bitch + // found a bitch var threadId = NativeMethods.GetWindowThreadProcessId(window, out processId); if (!registeredProcesses.Any(x => x.Id == processId)) - //not our bitch - continue; + continue; // not our bitch } + logger.LogTrace("Identified \"Network Accessibility\" window in owned process {0}", processId); var found = false; @@ -140,13 +141,14 @@ namespace Tgstation.Server.Host.Components.Watchdog var windowText = stringBuilder.ToString(); if (windowText == "Yes") { - //smash_button_meme.jpg + // smash_button_meme.jpg logger.LogTrace("Sending \"Yes\" button clicks..."); for (var J = 0; J < SendMessageCount; ++J) { const int BM_CLICK = 0x00F5; var result = NativeMethods.SendMessage(I, BM_CLICK, IntPtr.Zero, IntPtr.Zero); } + found = true; break; } diff --git a/src/Tgstation.Server.Host/Configuration/ControlPanelConfiguration.cs b/src/Tgstation.Server.Host/Configuration/ControlPanelConfiguration.cs new file mode 100644 index 0000000000..719ccef9e0 --- /dev/null +++ b/src/Tgstation.Server.Host/Configuration/ControlPanelConfiguration.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; + +namespace Tgstation.Server.Host.Configuration +{ + /// + /// Configuration options for the web control panel + /// + public sealed class ControlPanelConfiguration + { + /// + /// The key for the the resides in + /// + public const string Section = "ControlPanel"; + + /// + /// If the control panel is enabled + /// + public bool Enable { get; set; } + + /// + /// If any origin is allowed for CORS requests. This overrides + /// + public bool AllowAnyOrigin { get; set; } + + /// + /// Origins allowed for CORS requests + /// + public List AllowedOrigins { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Configuration/DatabaseType.cs b/src/Tgstation.Server.Host/Configuration/DatabaseType.cs index 2ca7d8fafc..c4b5f31d67 100644 --- a/src/Tgstation.Server.Host/Configuration/DatabaseType.cs +++ b/src/Tgstation.Server.Host/Configuration/DatabaseType.cs @@ -9,10 +9,12 @@ /// Use Microsoft SQL Server /// SqlServer, + /// /// Use MySQL /// MySql, + /// /// Use MariaDB /// diff --git a/src/Tgstation.Server.Host/Configuration/FileLoggingConfiguration.cs b/src/Tgstation.Server.Host/Configuration/FileLoggingConfiguration.cs index e10a6634ed..0251efb803 100644 --- a/src/Tgstation.Server.Host/Configuration/FileLoggingConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/FileLoggingConfiguration.cs @@ -28,7 +28,7 @@ namespace Tgstation.Server.Host.Configuration /// Where log files are stored /// public string Directory { get; set; } - + /// /// If file logging is disabled /// @@ -40,7 +40,6 @@ namespace Tgstation.Server.Host.Configuration [JsonConverter(typeof(StringEnumConverter))] public LogLevel LogLevel { get; set; } = DefaultLogLevel; - /// /// The minimum to display in logs for Microsoft library sources /// diff --git a/src/Tgstation.Server.Host/Configuration/SetupWizardMode.cs b/src/Tgstation.Server.Host/Configuration/SetupWizardMode.cs index 4f2b59204a..b5039d15c8 100644 --- a/src/Tgstation.Server.Host/Configuration/SetupWizardMode.cs +++ b/src/Tgstation.Server.Host/Configuration/SetupWizardMode.cs @@ -9,14 +9,17 @@ /// Run the wizard if the appsettings.{Environment}.json is not present or empty /// Autodetect, + /// /// Force run the wizard /// Force, + /// /// Only run the wizard and exit /// Only, + /// /// Never run the wizard /// diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs index 0899ab7db5..605be61107 100644 --- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs +++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs @@ -8,7 +8,6 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; -using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api; @@ -126,6 +125,7 @@ namespace Tgstation.Server.Host.Controllers { Logger.LogWarning("Not found exception while retrieving upstream repository info: {0}", e); } + return Json(new Administration { LatestVersion = greatestVersion, @@ -188,19 +188,22 @@ namespace Tgstation.Server.Host.Controllers var asset = release.Assets.Where(x => x.Name == updatesConfiguration.UpdatePackageAssetName).FirstOrDefault(); if (asset == default) continue; - + if (!serverUpdater.ApplyUpdate(version, new Uri(asset.BrowserDownloadUrl), ioManager)) return Conflict(new ErrorMessage { Message = "An update operation is already in progress!" }); - return Accepted(); //gtfo of here before all the cancellation tokens fire + return Accepted(); // gtfo of here before all the cancellation tokens fire } return StatusCode((int)HttpStatusCode.Gone); } - /// + /// + /// Attempts to restart the server + /// + /// A resulting in the of the request [HttpDelete] [TgsAuthorize(AdministrationRights.RestartHost)] public async Task Delete() @@ -215,6 +218,7 @@ namespace Tgstation.Server.Host.Controllers Message = RestartNotSupportedException }); } + await serverUpdater.Restart().ConfigureAwait(false); return Ok(); } diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs index 3b26db1875..194901e380 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -51,6 +51,11 @@ namespace Tgstation.Server.Host.Controllers /// readonly bool requireInstance; + /// + /// If are required + /// + readonly bool requireHeaders; + /// /// Construct an /// @@ -58,7 +63,8 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// The value of /// The value of - public ApiController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger, bool requireInstance) + /// The value of + public ApiController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger, bool requireInstance, bool requireHeaders) { DatabaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext)); if (authenticationContextFactory == null) @@ -67,22 +73,23 @@ namespace Tgstation.Server.Host.Controllers AuthenticationContext = authenticationContextFactory.CurrentAuthenticationContext; Instance = AuthenticationContext?.InstanceUser?.Instance; this.requireInstance = requireInstance; + this.requireHeaders = requireHeaders; } /// + #pragma warning disable CA1506 // TODO: Decomplexify public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { - //ALL valid token and login requests that match a route go through this function - //404 is returned before - + // ALL valid token and login requests that match a route go through this function + // 404 is returned before if (AuthenticationContext != null && AuthenticationContext.User == null) { - //valid token, expired password + // valid token, expired password await Unauthorized().ExecuteResultAsync(context).ConfigureAwait(false); return; } - //validate the headers + // validate the headers try { ApiHeaders = new ApiHeaders(Request.GetTypedHeaders()); @@ -103,9 +110,10 @@ namespace Tgstation.Server.Host.Controllers await BadRequest(new ErrorMessage { Message = "Missing Instance header!" }).ExecuteResultAsync(context).ConfigureAwait(false); return; } + if (AuthenticationContext.InstanceUser == null) { - //accessing an instance they don't have access to or one that's disabled + // accessing an instance they don't have access to or one that's disabled await Forbid().ExecuteResultAsync(context).ConfigureAwait(false); return; } @@ -113,15 +121,19 @@ namespace Tgstation.Server.Host.Controllers } catch (InvalidOperationException e) { - await BadRequest(new ErrorMessage { Message = e.Message }).ExecuteResultAsync(context).ConfigureAwait(false); - return; + if (requireHeaders) + { + await BadRequest(new ErrorMessage { Message = e.Message }).ExecuteResultAsync(context).ConfigureAwait(false); + return; + } } if (ModelState?.IsValid == false) { var errorMessages = ModelState.SelectMany(x => x.Value.Errors).Select(x => x.ErrorMessage).ToList(); - //HACK - //do some fuckery to remove RequiredAttribute errors + + // HACK + // do some fuckery to remove RequiredAttribute errors for (var I = 0; I < errorMessages.Count; ++I) { var message = errorMessages[I]; @@ -131,6 +143,7 @@ namespace Tgstation.Server.Host.Controllers --I; } } + if (errorMessages.Count > 0) { await BadRequest(new ErrorMessage { Message = String.Join(Environment.NewLine, errorMessages) }).ExecuteResultAsync(context).ConfigureAwait(false); @@ -138,7 +151,8 @@ namespace Tgstation.Server.Host.Controllers } } - Logger.LogDebug("Request made by User ID {0}. Api version: {1}. User-Agent: {2}. Type: {3}. Route {4}{5} to Instance {6}", AuthenticationContext?.User.Id.ToString(CultureInfo.InvariantCulture), ApiHeaders.ApiVersion, ApiHeaders.UserAgent, Request.Method, Request.Path, Request.QueryString, ApiHeaders.InstanceId); + if (ApiHeaders != null) + Logger.LogDebug("Request made by User ID {0}. Api version: {1}. User-Agent: {2}. Type: {3}. Route {4}{5} to Instance {6}", AuthenticationContext?.User.Id.ToString(CultureInfo.InvariantCulture), ApiHeaders.ApiVersion, ApiHeaders.UserAgent, Request.Method, Request.Path, Request.QueryString, ApiHeaders.InstanceId); try { @@ -150,5 +164,6 @@ namespace Tgstation.Server.Host.Controllers throw; } } + #pragma warning restore CA1506 } } diff --git a/src/Tgstation.Server.Host/Controllers/ByondController.cs b/src/Tgstation.Server.Host/Controllers/ByondController.cs index 49e4c38be7..10229aaef1 100644 --- a/src/Tgstation.Server.Host/Controllers/ByondController.cs +++ b/src/Tgstation.Server.Host/Controllers/ByondController.cs @@ -47,7 +47,7 @@ namespace Tgstation.Server.Host.Controllers /// [TgsAuthorize(ByondRights.ReadActive)] - public override Task Read(CancellationToken cancellationToken) => Task.FromResult((IActionResult) + public override Task Read(CancellationToken cancellationToken) => Task.FromResult( Json(new Api.Models.Byond { Version = instanceManager.GetInstance(Instance).ByondManager.ActiveVersion @@ -55,7 +55,7 @@ namespace Tgstation.Server.Host.Controllers /// [TgsAuthorize(ByondRights.ListInstalled)] - public override Task List(CancellationToken cancellationToken) => Task.FromResult((IActionResult) + public override Task List(CancellationToken cancellationToken) => Task.FromResult( Json(instanceManager.GetInstance(Instance).ByondManager.InstalledVersions.Select(x => new Api.Models.Byond { Version = x @@ -73,7 +73,7 @@ namespace Tgstation.Server.Host.Controllers var byondManager = instanceManager.GetInstance(Instance).ByondManager; - //remove cruff fields + // remove cruff fields var installingVersion = new Version(model.Version.Major, model.Version.Minor); var result = new Api.Models.Byond(); @@ -86,7 +86,8 @@ namespace Tgstation.Server.Host.Controllers else { Logger.LogInformation("User ID {0} installing BYOND version to {2} on instance ID {1}", AuthenticationContext.User.Id, Instance.Id, installingVersion); - //run the install through the job manager + + // run the install through the job manager var job = new Models.Job { Description = String.Format(CultureInfo.InvariantCulture, "Install BYOND version {0}", installingVersion), @@ -98,6 +99,7 @@ namespace Tgstation.Server.Host.Controllers await jobManager.RegisterOperation(job, (paramJob, databaseContext, progressHandler, ct) => byondManager.ChangeVersion(installingVersion, ct), cancellationToken).ConfigureAwait(false); result.InstallJob = job.ToApi(); } + if ((AuthenticationContext.GetRight(RightsType.Byond) & (ulong)ByondRights.ReadActive) != 0) result.Version = byondManager.ActiveVersion; return result.InstallJob != null ? (IActionResult)Accepted(result) : Json(result); diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index 2e6d3a9ebc..26ac8bc834 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -87,13 +87,13 @@ namespace Tgstation.Server.Host.Controllers model.Enabled = model.Enabled ?? false; - //try to update das db first + // try to update das db first var dbModel = new Models.ChatBot { Name = model.Name, ConnectionString = model.ConnectionString, Enabled = model.Enabled, - Channels = model.Channels?.Select(x => ConvertApiChatChannel(x)).ToList() ?? new List(), //important that this isn't null + Channels = model.Channels?.Select(x => ConvertApiChatChannel(x)).ToList() ?? new List(), // important that this isn't null InstanceId = Instance.Id, Provider = model.Provider, }; @@ -106,7 +106,7 @@ namespace Tgstation.Server.Host.Controllers { try { - //try to create it + // try to create it var instance = instanceManager.GetInstance(Instance); await instance.Chat.ChangeSettings(dbModel, cancellationToken).ConfigureAwait(false); @@ -115,7 +115,7 @@ namespace Tgstation.Server.Host.Controllers } catch { - //undo the add + // undo the add DatabaseContext.ChatBots.Remove(dbModel); await DatabaseContext.Save(default).ConfigureAwait(false); throw; @@ -125,6 +125,7 @@ namespace Tgstation.Server.Host.Controllers { return BadRequest(new ErrorMessage { Message = e.Message }); } + return StatusCode((int)HttpStatusCode.Created, dbModel.ToApi()); } @@ -174,6 +175,7 @@ namespace Tgstation.Server.Host.Controllers } /// + #pragma warning disable CA1506 // TODO: Decomplexify [TgsAuthorize(ChatBotRights.WriteChannels | ChatBotRights.WriteConnectionString | ChatBotRights.WriteEnabled | ChatBotRights.WriteName | ChatBotRights.WriteProvider)] public override async Task Update([FromBody] Api.Models.ChatBot model, CancellationToken cancellationToken) { @@ -208,7 +210,7 @@ namespace Tgstation.Server.Host.Controllers property.SetValue(current, newVal); anySettingsModified = true; return false; - }; + } var oldProvider = current.Provider; @@ -238,8 +240,7 @@ namespace Tgstation.Server.Host.Controllers var chat = instanceManager.GetInstance(Instance).Chat; if (anySettingsModified) - //have to rebuild the thing first - await chat.ChangeSettings(current, cancellationToken).ConfigureAwait(false); + await chat.ChangeSettings(current, cancellationToken).ConfigureAwait(false); // have to rebuild the thing first if (model.Channels != null || anySettingsModified) await chat.ChangeChannels(current.Id, current.Channels, cancellationToken).ConfigureAwait(false); @@ -250,7 +251,9 @@ namespace Tgstation.Server.Host.Controllers current.ConnectionString = null; return Json(current.ToApi()); } + return Ok(); } + #pragma warning restore CA1506 } } diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index a893ea3735..49bbb78e67 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -58,6 +58,7 @@ namespace Tgstation.Server.Host.Controllers systemIdentityToUse = null; return true; } + systemIdentityToUse = Instance.ConfigurationType == ConfigurationType.SystemIdentityWrite ? AuthenticationContext.SystemIdentity : null; return false; } diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index ed43af0cf2..3a33d10457 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -29,6 +29,7 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// readonly IJobManager jobManager; + /// /// The for the /// @@ -52,7 +53,7 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize(DreamDaemonRights.Start)] public override async Task Create([FromBody] DreamDaemon model, CancellationToken cancellationToken) { - //alias for launching DD + // alias for launching DD var instance = instanceManager.GetInstance(Instance); if (instance.Watchdog.Running) @@ -94,7 +95,7 @@ namespace Tgstation.Server.Host.Controllers if (settings == default) return StatusCode((int)HttpStatusCode.Gone); } - + var result = new DreamDaemon(); if (metadata) { @@ -113,7 +114,7 @@ namespace Tgstation.Server.Host.Controllers result.SoftRestart = rstate == RebootState.Restart; result.SoftShutdown = rstate == RebootState.Shutdown; result.StartupTimeout = settings.StartupTimeout; - }; + } if (revision) { @@ -126,18 +127,22 @@ namespace Tgstation.Server.Host.Controllers return Json(result); } - /// + /// + /// Stops DreamDaemon if it's running + /// + /// The for the operation + /// A resulting in the of the operation [HttpDelete] [TgsAuthorize(DreamDaemonRights.Shutdown)] public async Task Delete(CancellationToken cancellationToken) { - //alias for stopping DD var instance = instanceManager.GetInstance(Instance); await instance.Watchdog.Terminate(false, cancellationToken).ConfigureAwait(false); return Ok(); } /// + #pragma warning disable CA1506 // TODO: Decomplexify [TgsAuthorize(DreamDaemonRights.SetAutoStart | DreamDaemonRights.SetPorts | DreamDaemonRights.SetSecurity | DreamDaemonRights.SetWebClient | DreamDaemonRights.SoftRestart | DreamDaemonRights.SoftShutdown | DreamDaemonRights.Start | DreamDaemonRights.SetStartupTimeout)] public override async Task Update([FromBody] DreamDaemon model, CancellationToken cancellationToken) { @@ -150,7 +155,7 @@ namespace Tgstation.Server.Host.Controllers if (model.SecurityLevel == DreamDaemonSecurity.Ultrasafe) return BadRequest(new ErrorMessage { Message = "This version of TGS does not support the ultrasafe DreamDaemon configuration!" }); - //alias for changing DD settings + // alias for changing DD settings var current = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (current == default) @@ -171,7 +176,7 @@ namespace Tgstation.Server.Host.Controllers property.SetValue(current, newVal); return false; - }; + } var oldSoftRestart = current.SoftRestart; var oldSoftShutdown = current.SoftShutdown; @@ -190,9 +195,10 @@ namespace Tgstation.Server.Host.Controllers return BadRequest(new ErrorMessage { Message = "Primary port and secondary port cannot be the same!" }); var wd = instanceManager.GetInstance(Instance).Watchdog; - + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); - //run this second because current may be modified by it + + // run this second because current may be modified by it await wd.ChangeSettings(current, cancellationToken).ConfigureAwait(false); if (!oldSoftRestart.Value && current.SoftRestart.Value) @@ -204,6 +210,7 @@ namespace Tgstation.Server.Host.Controllers return await ReadImpl(current, cancellationToken).ConfigureAwait(false); } + #pragma warning restore CA1506 /// /// Handle a HTTP PATCH to the diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index 2e4a718ac1..f9d0a57451 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -20,12 +20,14 @@ namespace Tgstation.Server.Host.Controllers /// Controller for managing the compiler /// [Route(Routes.DreamMaker)] - public sealed class DreamMakerController : ModelController + #pragma warning disable CA1506 // TODO: Decomplexify + public sealed class DreamMakerController : ModelController { /// /// The for the /// readonly IJobManager jobManager; + /// /// The for the /// diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index 3e590e7e97..1c31b949b4 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -1,15 +1,19 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using System; using System.Linq; -using System.Net.Http.Headers; +using System.Net.Mime; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api; +using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; +using Wangkanai.Detection; namespace Tgstation.Server.Host.Controllers { @@ -23,23 +27,37 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// readonly ITokenFactory tokenFactory; + /// /// The for the /// readonly ISystemIdentityFactory systemIdentityFactory; + /// /// The for the /// readonly ICryptographySuite cryptographySuite; + /// /// The for the /// readonly IApplication application; + /// /// The for the /// readonly IIdentityCache identityCache; + /// + /// The for the + /// + readonly IBrowserResolver browserResolver; + + /// + /// The for the + /// + readonly ControlPanelConfiguration controlPanelConfiguration; + /// /// Construct a /// @@ -50,27 +68,45 @@ namespace Tgstation.Server.Host.Controllers /// The value of /// The value of /// The value of + /// The value of + /// The containing the value of /// The for the - public HomeController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ITokenFactory tokenFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite, IApplication application, IIdentityCache identityCache, ILogger logger) : base(databaseContext, authenticationContextFactory, logger, false) + public HomeController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ITokenFactory tokenFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite, IApplication application, IIdentityCache identityCache, IBrowserResolver browserResolver, IOptions controlPanelConfigurationOptions, ILogger logger) : base(databaseContext, authenticationContextFactory, logger, false, false) { this.tokenFactory = tokenFactory ?? throw new ArgumentNullException(nameof(tokenFactory)); this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory)); this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); this.application = application ?? throw new ArgumentNullException(nameof(application)); this.identityCache = identityCache ?? throw new ArgumentNullException(nameof(identityCache)); + this.browserResolver = browserResolver ?? throw new ArgumentNullException(nameof(browserResolver)); + controlPanelConfiguration = controlPanelConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(controlPanelConfigurationOptions)); } /// - /// Returns the version of the + /// Main page of the /// - /// + /// The of the if a properly authenticated API request, the web control panel if on a browser and enabled, otherwise [TgsAuthorize] + [AllowAnonymous] [HttpGet] - public JsonResult Home() => Json(new Api.Models.ServerInformation + public IActionResult Home() { - Version = application.Version, - ApiVersion = ApiHeaders.Version - }); + if (AuthenticationContext != null) + return Json(new Api.Models.ServerInformation + { + Version = application.Version, + ApiVersion = ApiHeaders.Version + }); + + // if we are using a browser and the control panel, soft redirect to the app page + if (controlPanelConfiguration.Enable && browserResolver.Browser.Type != BrowserType.Generic) + { + Logger.LogDebug("Unauthorized browser request (User-Agent: \"{0}\"), loading control panel...", browserResolver.UserAgent); + return File("~/index.html", MediaTypeNames.Text.Html); + } + + return Unauthorized(); + } /// /// Attempt to authenticate a using @@ -78,21 +114,26 @@ namespace Tgstation.Server.Host.Controllers /// The for the operation /// A resulting in the of the operation [HttpPost] + #pragma warning disable CA1506 // TODO: Decomplexify public async Task CreateToken(CancellationToken cancellationToken) { + if (ApiHeaders == null) + return BadRequest(new Api.Models.ErrorMessage { Message = "Missing API headers!" }); + if (ApiHeaders.IsTokenAuthentication) return BadRequest(new Api.Models.ErrorMessage { Message = "Cannot create a token using another token!" }); ISystemIdentity identity; try { - //trust the system over the database because a user's name can change while still having the same SID + // trust the system over the database because a user's name can change while still having the same SID identity = await systemIdentityFactory.CreateSystemIdentity(ApiHeaders.Username, ApiHeaders.Password, cancellationToken).ConfigureAwait(false); } catch (NotImplementedException) { identity = null; } + using (identity) { IQueryable query; @@ -127,7 +168,8 @@ namespace Tgstation.Server.Host.Controllers await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); } } - //check if the name changed and updoot accordingly + + // check if the name changed and updoot accordingly else if (identity.Username != user.Name) { DatabaseContext.Users.Attach(user); @@ -142,7 +184,7 @@ namespace Tgstation.Server.Host.Controllers var token = await tokenFactory.CreateToken(user, cancellationToken).ConfigureAwait(false); if (identity != null) { - //expire the identity slightly after the auth token in case of lag + // expire the identity slightly after the auth token in case of lag var identExpiry = token.ExpiresAt.Value; identExpiry += tokenFactory.ValidationParameters.ClockSkew; identExpiry += TimeSpan.FromSeconds(15); @@ -154,5 +196,6 @@ namespace Tgstation.Server.Host.Controllers return Json(token); } } + #pragma warning restore CA1506 } } diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index d50d5a09af..22419b3be2 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -26,6 +26,7 @@ namespace Tgstation.Server.Host.Controllers /// Controller for managing s /// [Route(Routes.InstanceManager)] + #pragma warning disable CA1506 // TODO: Decomplexify public sealed class InstanceController : ModelController { /// @@ -87,6 +88,7 @@ namespace Tgstation.Server.Host.Controllers absolutePath = null; return; } + absolutePath = ioManager.ResolvePath(model.Path); if (platformIdentifier.IsWindows) model.Path = absolutePath.ToUpperInvariant(); @@ -130,7 +132,6 @@ namespace Tgstation.Server.Host.Controllers if (rawPath.StartsWith(normalizedLocalPath, StringComparison.Ordinal)) return Conflict("Instances cannot be created in the installation directory!"); - var dirExistsTask = ioManager.DirectoryExists(model.Path, cancellationToken); bool attached = false; if (await ioManager.FileExists(model.Path, cancellationToken).ConfigureAwait(false) || await dirExistsTask.ConfigureAwait(false)) @@ -172,8 +173,7 @@ namespace Tgstation.Server.Host.Controllers AutoUpdatesSynchronize = false, PostTestMergeComment = false }, - //give this user full privileges on the instance - InstanceUsers = new List + InstanceUsers = new List // give this user full privileges on the instance { InstanceAdminUser() } @@ -186,13 +186,13 @@ namespace Tgstation.Server.Host.Controllers try { - //actually reserve it now + // actually reserve it now await ioManager.CreateDirectory(rawPath, cancellationToken).ConfigureAwait(false); await ioManager.DeleteFile(ioManager.ConcatPath(rawPath, InstanceAttachFileName), cancellationToken).ConfigureAwait(false); } catch { - //oh shit delete the model + // oh shit delete the model DatabaseContext.Instances.Remove(newInstance); await DatabaseContext.Save(default).ConfigureAwait(false); @@ -244,12 +244,13 @@ namespace Tgstation.Server.Host.Controllers var attachFileName = ioManager.ConcatPath(originalModel.Path, InstanceAttachFileName); await ioManager.WriteAllBytes(attachFileName, Array.Empty(), default).ConfigureAwait(false); - await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); //cascades everything + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); // cascades everything return Ok(); } /// [TgsAuthorize(InstanceManagerRights.Relocate | InstanceManagerRights.Rename | InstanceManagerRights.SetAutoUpdate | InstanceManagerRights.SetConfiguration | InstanceManagerRights.SetOnline)] + #pragma warning disable CA1502 // TODO: Decomplexify public override async Task Update([FromBody] Api.Models.Instance model, CancellationToken cancellationToken) { var instanceQuery = DatabaseContext.Instances.Where(x => x.Id == model.Id); @@ -265,8 +266,7 @@ namespace Tgstation.Server.Host.Controllers }).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (moveJob != default) - //cancel it now - await jobManager.CancelJob(moveJob, AuthenticationContext.User, true, cancellationToken).ConfigureAwait(false); + await jobManager.CancelJob(moveJob, AuthenticationContext.User, true, cancellationToken).ConfigureAwait(false); // cancel it now var usersInstanceUserTask = instanceQuery.SelectMany(x => x.InstanceUsers).Where(x => x.UserId == AuthenticationContext.User.Id).FirstOrDefaultAsync(cancellationToken); @@ -274,7 +274,7 @@ namespace Tgstation.Server.Host.Controllers .Include(x => x.RepositorySettings) .Include(x => x.ChatSettings) .ThenInclude(x => x.Channels) - .Include(x => x.DreamDaemonSettings) //need these for onlining + .Include(x => x.DreamDaemonSettings) // need these for onlining .FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (originalModel == default(Models.Instance)) return StatusCode((int)HttpStatusCode.Gone); @@ -293,7 +293,7 @@ namespace Tgstation.Server.Host.Controllers property.SetValue(originalModel, newVal); return false; - }; + } string originalModelPath = null; string rawPath = null; @@ -327,7 +327,7 @@ namespace Tgstation.Server.Host.Controllers || CheckModified(x => x.Online, InstanceManagerRights.SetOnline)) return Forbid(); - //ensure the current user has write privilege on the instance + // ensure the current user has write privilege on the instance var usersInstanceUser = await usersInstanceUserTask.ConfigureAwait(false); if (usersInstanceUser == default) { @@ -350,8 +350,8 @@ namespace Tgstation.Server.Host.Controllers await instanceManager.OfflineInstance(originalModel, AuthenticationContext.User, cancellationToken).ConfigureAwait(false); else if (!originalOnline && model.Online == true) { - //force autostart false here because we don't want any long running jobs right now - //remember to document this + // force autostart false here because we don't want any long running jobs right now + // remember to document this originalModel.DreamDaemonSettings.AutoStart = false; await instanceManager.OnlineInstance(originalModel, cancellationToken).ConfigureAwait(false); } @@ -392,6 +392,7 @@ namespace Tgstation.Server.Host.Controllers return Json(api); } + #pragma warning restore CA1502 /// [TgsAuthorize(InstanceManagerRights.List | InstanceManagerRights.Read)] @@ -413,7 +414,7 @@ namespace Tgstation.Server.Host.Controllers var apis = instances.Select(x => x.ToApi()); var moveJobs = await moveJobTasks.ConfigureAwait(false); foreach(var I in moveJobs) - apis.Where(x => x.Id == I.Instance.Id).First().MoveJob = I.ToApi(); //if this .First() fails i will personally murder kevinz000 because I just know he is somehow responsible + apis.Where(x => x.Id == I.Instance.Id).First().MoveJob = I.ToApi(); // if this .First() fails i will personally murder kevinz000 because I just know he is somehow responsible return Json(apis); } @@ -441,7 +442,7 @@ namespace Tgstation.Server.Host.Controllers if (cantList && !instance.InstanceUsers.Any(x => x.UserId == AuthenticationContext.User.Id && x.AnyRights)) return Forbid(); - + var api = instance.ToApi(); api.MoveJob = (await moveJobTask.ConfigureAwait(false))?.ToApi(); return Json(api); diff --git a/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs index e28011d67c..67a4d0189d 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs @@ -27,7 +27,7 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// The for the /// The for the - public InstanceUserController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger) : base(databaseContext, authenticationContextFactory, logger, true) //false instance requirement, we handle this ourself + public InstanceUserController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger) : base(databaseContext, authenticationContextFactory, logger, true) // false instance requirement, we handle this ourself { } /// @@ -75,6 +75,7 @@ namespace Tgstation.Server.Host.Controllers /// [TgsAuthorize(InstanceUserRights.WriteUsers)] + #pragma warning disable CA1506 // TODO: Decomplexify public override async Task Update([FromBody] Api.Models.InstanceUser model, CancellationToken cancellationToken) { var test = StandardModelChecks(model); @@ -99,6 +100,7 @@ namespace Tgstation.Server.Host.Controllers UserId = originalUser.UserId }); } + #pragma warning restore CA1506 /// [TgsAuthorize] @@ -116,7 +118,7 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize(InstanceUserRights.ReadUsers)] public override async Task GetId(long id, CancellationToken cancellationToken) { - //this functions as userId + // this functions as userId var user = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).SelectMany(x => x.InstanceUsers).Where(x => x.UserId == id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (user == default) return StatusCode((int)HttpStatusCode.Gone); diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs index d959a9a0eb..e5c08bbc99 100644 --- a/src/Tgstation.Server.Host/Controllers/JobController.cs +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -48,7 +48,7 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize] public override async Task List(CancellationToken cancellationToken) { - //you KNOW this will need pagination eventually right? + // you KNOW this will need pagination eventually right? var jobs = await DatabaseContext.Jobs.Where(x => x.Instance.Id == Instance.Id).OrderByDescending(x => x.StartedAt).Select(x => new Api.Models.Job { Id = x.Id @@ -60,7 +60,7 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize] public override async Task Delete(long id, CancellationToken cancellationToken) { - //don't care if an instance post or not at this point + // don't care if an instance post or not at this point var job = await DatabaseContext.Jobs.Where(x => x.Id == id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (job == default(Job)) return NotFound(); diff --git a/src/Tgstation.Server.Host/Controllers/ModelController.cs b/src/Tgstation.Server.Host/Controllers/ModelController.cs index 4dc35d41c3..3434992040 100644 --- a/src/Tgstation.Server.Host/Controllers/ModelController.cs +++ b/src/Tgstation.Server.Host/Controllers/ModelController.cs @@ -20,7 +20,7 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// The for the /// If the requires an - public ModelController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger, bool requireInstance) : base(databaseContext, authenticationContextFactory, logger, requireInstance) { } + public ModelController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger, bool requireInstance) : base(databaseContext, authenticationContextFactory, logger, requireInstance, true) { } /// /// Attempt to create a diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index c945aa1918..69d0d6417b 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -26,6 +26,7 @@ namespace Tgstation.Server.Host.Controllers /// Controller for managing the s /// [Route(Routes.Repository)] + #pragma warning disable CA1506 // TODO: Decomplexify public sealed class RepositoryController : ModelController { /// @@ -83,24 +84,26 @@ namespace Tgstation.Server.Host.Controllers var needsDbUpdate = revisionInfo == default; if (needsDbUpdate) { - //needs insertion + // needs insertion revisionInfo = new Models.RevisionInformation { Instance = instance, CommitSha = repoSha, CompileJobs = new List(), - ActiveTestMerges = new List() //non null vals for api returns + ActiveTestMerges = new List() // non null vals for api returns }; - lock (databaseContext) //cleaner this way + lock (databaseContext) // cleaner this way databaseContext.RevisionInformations.Add(revisionInfo); } + revisionInfo.OriginCommitSha = revisionInfo.OriginCommitSha ?? lastOriginCommitSha; if (revisionInfo.OriginCommitSha == null) { revisionInfo.OriginCommitSha = repoSha; Logger.LogWarning(Components.Repository.Repository.OriginTrackingErrorTemplate, repoSha); } + revInfoSink?.Invoke(revisionInfo); return needsDbUpdate; } @@ -112,10 +115,11 @@ namespace Tgstation.Server.Host.Controllers model.GitHubOwner = repository.GitHubOwner; model.GitHubName = repository.GitHubRepoName; } + model.Origin = repository.Origin; model.Reference = repository.Reference; - //rev info stuff + // rev info stuff Models.RevisionInformation revisionInfo = null; var needsDbUpdate = await LoadRevisionInformation(repository, databaseContext, instance, null, x => revisionInfo = x, cancellationToken).ConfigureAwait(false); model.RevisionInformation = revisionInfo.ToApi(); @@ -140,7 +144,7 @@ namespace Tgstation.Server.Host.Controllers if (currentModel == default) return StatusCode((int)HttpStatusCode.Gone); - //normalize github urls + // normalize github urls const string BadGitHubUrl = "://www.github.com/"; var uiOrigin = model.Origin.ToUpperInvariant(); var uiBad = BadGitHubUrl.ToUpperInvariant(); @@ -149,7 +153,7 @@ namespace Tgstation.Server.Host.Controllers model.Origin = uiOrigin.Replace(uiBad, uiGitHub, StringComparison.Ordinal); currentModel.AccessToken = model.AccessToken; - currentModel.AccessUser = model.AccessUser; //intentionally only these fields, user not allowed to change anything else atm + currentModel.AccessUser = model.AccessUser; // intentionally only these fields, user not allowed to change anything else atm var cloneBranch = model.Reference; var origin = model.Origin; @@ -169,8 +173,8 @@ namespace Tgstation.Server.Host.Controllers using (var repo = await repoManager.LoadRepository(cancellationToken).ConfigureAwait(false)) { + // clone conflict if (repo != null) - //clone conflict return Conflict(new ErrorMessage { Message = "The repository already exists!" @@ -269,16 +273,19 @@ namespace Tgstation.Server.Host.Controllers { if (repo != null && await PopulateApi(api, repo, DatabaseContext, Instance, cancellationToken).ConfigureAwait(false)) { - //user may have fucked with the repo without telling us, do what we can + // user may have fucked with the repo without telling us, do what we can await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); return StatusCode((int)HttpStatusCode.Created, api); } + return Json(api); } } /// [TgsAuthorize(RepositoryRights.ChangeAutoUpdateSettings | RepositoryRights.ChangeCommitter | RepositoryRights.ChangeCredentials | RepositoryRights.ChangeTestMergeCommits | RepositoryRights.MergePullRequest | RepositoryRights.SetReference | RepositoryRights.SetSha | RepositoryRights.UpdateBranch)] + #pragma warning disable CA1502 // TODO: Decomplexify + #pragma warning disable CA1505 public override async Task Update([FromBody]Repository model, CancellationToken cancellationToken) { if (model == null) @@ -331,7 +338,7 @@ namespace Tgstation.Server.Host.Controllers property.SetValue(currentModel, newVal); return false; - }; + } if (CheckModified(x => x.AccessToken, RepositoryRights.ChangeCredentials) || CheckModified(x => x.AccessUser, RepositoryRights.ChangeCredentials) @@ -347,7 +354,7 @@ namespace Tgstation.Server.Host.Controllers if (currentModel.AccessToken?.Length == 0 && currentModel.AccessUser?.Length == 0) { - //setting an empty string clears everything + // setting an empty string clears everything currentModel.AccessUser = null; currentModel.AccessToken = null; } @@ -381,11 +388,11 @@ namespace Tgstation.Server.Host.Controllers await PopulateApi(api, repo, DatabaseContext, Instance, cancellationToken).ConfigureAwait(false); } } - - //this is just db stuf so stow it away + + // this is just db stuf so stow it away await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); - //format the job description + // format the job description string description = null; if (model.UpdateFromOrigin == true) if (model.Reference != null) @@ -398,16 +405,15 @@ namespace Tgstation.Server.Host.Controllers description = String.Format(CultureInfo.InvariantCulture, "Checkout repository {0} {1}", model.Reference != null ? "reference" : "SHA", model.Reference ?? model.CheckoutSha); if (newTestMerges) - description = String.Format(CultureInfo.InvariantCulture, "{0}est merge pull request(s) {1}{2}", + description = String.Format(CultureInfo.InvariantCulture, "{0}est merge pull request(s) {1}{2}", description != null ? String.Format(CultureInfo.InvariantCulture, "{0} and t", description) : "T", - String.Join(", ", model.NewTestMerges.Select(x => - String.Format(CultureInfo.InvariantCulture, "#{0}{1}", x.Number, + String.Join(", ", model.NewTestMerges.Select(x => + String.Format(CultureInfo.InvariantCulture, "#{0}{1}", x.Number, x.PullRequestRevision != null ? String.Format(CultureInfo.InvariantCulture, " at {0}", x.PullRequestRevision.Substring(0, 7)) : String.Empty))), description != null ? String.Empty : " in repository"); if (description == null) - //no git changes - return Json(api); + return Json(api); // no git changes var job = new Models.Job { @@ -445,14 +451,14 @@ namespace Tgstation.Server.Host.Controllers { var tmpDoneSteps = doneSteps; ++doneSteps; - return progress => progressReporter((progress + 100 * tmpDoneSteps) / numSteps); - }; + return progress => progressReporter((progress + (100 * tmpDoneSteps)) / numSteps); + } progressReporter(0); - //get a base line for where we are + // get a base line for where we are Models.RevisionInformation lastRevisionInfo = null; - + var attachedInstance = new Models.Instance { Id = Instance.Id @@ -461,17 +467,17 @@ namespace Tgstation.Server.Host.Controllers await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, ct).ConfigureAwait(false); - //apply new rev info, tracking applied test merges + // apply new rev info, tracking applied test merges async Task UpdateRevInfo() { var last = lastRevisionInfo; await LoadRevisionInformation(repo, databaseContext, attachedInstance, last.OriginCommitSha, x => lastRevisionInfo = x, ct).ConfigureAwait(false); lastRevisionInfo.ActiveTestMerges.AddRange(last.ActiveTestMerges); - }; + } try { - //fetch/pull + // fetch/pull if (model.UpdateFromOrigin == true) { if (!repo.Tracking) @@ -495,7 +501,7 @@ namespace Tgstation.Server.Host.Controllers } } - //checkout/hard reset + // checkout/hard reset if (modelHasShaOrReference) { if ((model.CheckoutSha != null && repo.Head.ToUpperInvariant().StartsWith(model.CheckoutSha.ToUpperInvariant(), StringComparison.Ordinal)) @@ -508,7 +514,7 @@ namespace Tgstation.Server.Host.Controllers throw new JobException("Attempted to checkout a SHA or reference that was actually the opposite!"); await repo.CheckoutObject(committish, NextProgressReporter(), ct).ConfigureAwait(false); - await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, ct).ConfigureAwait(false); //we've either seen origin before or what we're checking out is on origin + await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, ct).ConfigureAwait(false); // we've either seen origin before or what we're checking out is on origin } else NextProgressReporter()(100); @@ -520,35 +526,36 @@ namespace Tgstation.Server.Host.Controllers await repo.ResetToOrigin(NextProgressReporter(), ct).ConfigureAwait(false); await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), true, ct).ConfigureAwait(false); await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, ct).ConfigureAwait(false); - //repo head is on origin so force this - //will update the db if necessary + + // repo head is on origin so force this + // will update the db if necessary lastRevisionInfo.OriginCommitSha = repo.Head; } } + // test merging Dictionary prMap = null; - //test merging if (newTestMerges) { - //bit of sanitization + // bit of sanitization foreach (var I in model.NewTestMerges.Where(x => String.IsNullOrWhiteSpace(x.PullRequestRevision))) I.PullRequestRevision = null; - var gitHubClient = currentModel.AccessToken != null - ? gitHubClientFactory.CreateClient(currentModel.AccessToken) + var gitHubClient = currentModel.AccessToken != null + ? gitHubClientFactory.CreateClient(currentModel.AccessToken) : (String.IsNullOrEmpty(generalConfiguration.GitHubAccessToken) - ? gitHubClientFactory.CreateClient() + ? gitHubClientFactory.CreateClient() : gitHubClientFactory.CreateClient(generalConfiguration.GitHubAccessToken)); var repoOwner = repo.GitHubOwner; var repoName = repo.GitHubRepoName; + // optimization: if we've already merged these exact same commits in this fashion before, just find the rev info for it and check it out Models.RevisionInformation revInfoWereLookingFor = null; bool needToApplyRemainingPrs = true; - //optimization: if we've already merged these exact same commits in this fashion before, just find the rev info for it and check it out if (lastRevisionInfo.OriginCommitSha == lastRevisionInfo.CommitSha) { - //In order for this to work though we need the shas of all the commits + // In order for this to work though we need the shas of all the commits if (model.NewTestMerges.Any(x => x.PullRequestRevision == null)) prMap = new Dictionary(); @@ -556,14 +563,13 @@ namespace Tgstation.Server.Host.Controllers foreach (var I in model.NewTestMerges) { if (I.PullRequestRevision != null) - //normalize the shas to lowercase ala libgit2 #pragma warning disable CA1308 // Normalize strings to uppercase - I.PullRequestRevision = I.PullRequestRevision?.ToLowerInvariant(); + I.PullRequestRevision = I.PullRequestRevision?.ToLowerInvariant(); // ala libgit2 #pragma warning restore CA1308 // Normalize strings to uppercase else - //retrieve the latest sha try { + // retrieve the latest sha var pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number.Value).ConfigureAwait(false); prMap.Add(I.Number.Value, pr); I.PullRequestRevision = pr.Head.Sha; @@ -586,7 +592,7 @@ namespace Tgstation.Server.Host.Controllers .ThenInclude(x => x.TestMerge) .ToListAsync(cancellationToken).ConfigureAwait(false); - //split here cause this bit has to be done locally + // split here cause this bit has to be done locally revInfoWereLookingFor = dbPull .Where(x => x.ActiveTestMerges.Count == model.NewTestMerges.Count && x.ActiveTestMerges.Select(y => y.TestMerge) @@ -598,7 +604,7 @@ namespace Tgstation.Server.Host.Controllers if (revInfoWereLookingFor == null && model.NewTestMerges.Count > 1) { - //okay try to add at least SOME prs we've seen before + // okay try to add at least SOME prs we've seen before var search = model.NewTestMerges.ToList(); var appliedTestMergeIds = new List(); @@ -624,7 +630,8 @@ namespace Tgstation.Server.Host.Controllers break; } } - } while (revInfoWereLookingFor != null && search.Count > 0); + } + while (revInfoWereLookingFor != null && search.Count > 0); revInfoWereLookingFor = lastGoodRevInfo; needToApplyRemainingPrs = search.Count != 0; @@ -638,14 +645,14 @@ namespace Tgstation.Server.Host.Controllers if (revInfoWereLookingFor != null) { - //goteem + // goteem await repo.ResetToSha(revInfoWereLookingFor.CommitSha, NextProgressReporter(), cancellationToken).ConfigureAwait(false); lastRevisionInfo = revInfoWereLookingFor; } if (needToApplyRemainingPrs) { - //an invocation of LoadRevisionInformation could have already loaded this user + // an invocation of LoadRevisionInformation could have already loaded this user var contextUser = databaseContext.Users.Local.Where(x => x.Id == AuthenticationContext.User.Id).FirstOrDefault(); if (contextUser == default) { @@ -668,13 +675,13 @@ namespace Tgstation.Server.Host.Controllers try { - //load from cache if possible + // load from cache if possible if (prMap == null || !prMap.TryGetValue(I.Number.Value, out pr)) pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number.Value).ConfigureAwait(false); } catch (Octokit.RateLimitExceededException) { - //you look at your anonymous access and sigh + // you look at your anonymous access and sigh errorMessage = "P.R.E. RATE LIMITED"; } catch (Octokit.AuthorizationException) @@ -683,11 +690,11 @@ namespace Tgstation.Server.Host.Controllers } catch (Octokit.NotFoundException) { - //you look at your shithub and sigh + // you look at your shithub and sigh errorMessage = "P.R.E. NOT FOUND"; } - //we want to take the earliest truth possible to prevent RCEs, if this fails AddTestMerge will set it + // we want to take the earliest truth possible to prevent RCEs, if this fails AddTestMerge will set it if (I.PullRequestRevision == null && pr != null) I.PullRequestRevision = pr.Head.Sha; @@ -730,13 +737,15 @@ namespace Tgstation.Server.Host.Controllers await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), false, ct).ConfigureAwait(false); await UpdateRevInfo().ConfigureAwait(false); } + await databaseContext.Save(ct).ConfigureAwait(false); } catch { doneSteps = 0; numSteps = 2; - //the stuff didn't make it into the db, forget what we've done and abort + + // the stuff didn't make it into the db, forget what we've done and abort await repo.CheckoutObject(startReference ?? startSha, NextProgressReporter(), default).ConfigureAwait(false); if (startReference != null && repo.Head != startSha) await repo.ResetToSha(startSha, NextProgressReporter(), default).ConfigureAwait(false); @@ -750,5 +759,7 @@ namespace Tgstation.Server.Host.Controllers api.ActiveJob = job.ToApi(); return Accepted(api); } + #pragma warning restore CA1502 + #pragma warning restore CA1505 } } diff --git a/src/Tgstation.Server.Host/Controllers/TgsAuthorizeAttribute.cs b/src/Tgstation.Server.Host/Controllers/TgsAuthorizeAttribute.cs index d6eb1b25b6..0d52ac6ae1 100644 --- a/src/Tgstation.Server.Host/Controllers/TgsAuthorizeAttribute.cs +++ b/src/Tgstation.Server.Host/Controllers/TgsAuthorizeAttribute.cs @@ -7,6 +7,7 @@ namespace Tgstation.Server.Host.Controllers /// /// Helper for using the with the system /// + #pragma warning disable CA1019 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] sealed class TgsAuthorizeAttribute : AuthorizeAttribute { @@ -19,54 +20,81 @@ namespace Tgstation.Server.Host.Controllers /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(AdministrationRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); + public TgsAuthorizeAttribute(AdministrationRights requiredRights) + { + Roles = RightsHelper.RoleNames(requiredRights); + } /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(InstanceManagerRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); + public TgsAuthorizeAttribute(InstanceManagerRights requiredRights) + { + Roles = RightsHelper.RoleNames(requiredRights); + } /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(RepositoryRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); + public TgsAuthorizeAttribute(RepositoryRights requiredRights) + { + Roles = RightsHelper.RoleNames(requiredRights); + } /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(ByondRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); + public TgsAuthorizeAttribute(ByondRights requiredRights) + { + Roles = RightsHelper.RoleNames(requiredRights); + } /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(DreamMakerRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); + public TgsAuthorizeAttribute(DreamMakerRights requiredRights) + { + Roles = RightsHelper.RoleNames(requiredRights); + } /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(DreamDaemonRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); + public TgsAuthorizeAttribute(DreamDaemonRights requiredRights) + { + Roles = RightsHelper.RoleNames(requiredRights); + } /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(ChatBotRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); + public TgsAuthorizeAttribute(ChatBotRights requiredRights) + { + Roles = RightsHelper.RoleNames(requiredRights); + } /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(ConfigurationRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); + public TgsAuthorizeAttribute(ConfigurationRights requiredRights) + { + Roles = RightsHelper.RoleNames(requiredRights); + } /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(InstanceUserRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); + public TgsAuthorizeAttribute(InstanceUserRights requiredRights) + { + Roles = RightsHelper.RoleNames(requiredRights); + } } } diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 05b5234e0c..fa37e32823 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -2,6 +2,7 @@ using Cyberboss.AspNetCore.AsyncInitializer; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Cors.Infrastructure; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Configuration; @@ -19,7 +20,6 @@ using System; using System.Globalization; using System.IdentityModel.Tokens.Jwt; using System.Reflection; -using System.Runtime.InteropServices; using System.Threading.Tasks; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Components.Byond; @@ -34,6 +34,7 @@ using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Core { /// + #pragma warning disable CA1506 sealed class Application : IApplication { /// @@ -90,19 +91,20 @@ namespace Tgstation.Server.Host.Core if (services == null) throw new ArgumentNullException(nameof(services)); - //needful + // needful services.AddSingleton(this); - //configure configuration - services.Configure(configuration.GetSection(UpdatesConfiguration.Section)); - services.Configure(configuration.GetSection(DatabaseConfiguration.Section)); - services.Configure(configuration.GetSection(GeneralConfiguration.Section)); - services.Configure(configuration.GetSection(FileLoggingConfiguration.Section)); + // configure configuration + services.UseStandardConfig(configuration); + services.UseStandardConfig(configuration); + services.UseStandardConfig(configuration); + services.UseStandardConfig(configuration); + services.UseStandardConfig(configuration); - //enable options which give us config reloading + // enable options which give us config reloading services.AddOptions(); - - //other stuff needed for for setup wizard and configuration + + // other stuff needed for for setup wizard and configuration services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -113,27 +115,27 @@ namespace Tgstation.Server.Host.Core GeneralConfiguration generalConfiguration; DatabaseConfiguration databaseConfiguration; FileLoggingConfiguration fileLoggingConfiguration; + ControlPanelConfiguration controlPanelConfiguration; IIOManager ioManager; IPlatformIdentifier platformIdentifier; - //temporarily build the service provider in it's current state - //do it here so we can run the setup wizard if necessary - //also allows us to get some options and other services we need for continued configuration + // temporarily build the service provider in it's current state + // do it here so we can run the setup wizard if necessary + // also allows us to get some options and other services we need for continued configuration using (var provider = services.BuildServiceProvider()) { - //run the wizard if necessary + // run the wizard if necessary var setupWizard = provider.GetRequiredService(); var applicationLifetime = provider.GetRequiredService(); var setupWizardRan = setupWizard.CheckRunWizard(applicationLifetime.ApplicationStopping).GetAwaiter().GetResult(); - //load the configuration options we need + // load the configuration options we need var generalOptions = provider.GetRequiredService>(); generalConfiguration = generalOptions.Value; - //unless this is set, in which case, we leave + // unless this is set, in which case, we leave if (setupWizardRan && generalConfiguration.SetupWizardMode == SetupWizardMode.Only) - //we don't inject a logger in the constuctor to log this because it's not yet configured - throw new OperationCanceledException("Exiting due to SetupWizardMode configuration!"); + throw new OperationCanceledException("Exiting due to SetupWizardMode configuration!"); // we don't inject a logger in the constuctor to log this because it's not yet configured var dbOptions = provider.GetRequiredService>(); databaseConfiguration = dbOptions.Value; @@ -141,15 +143,18 @@ namespace Tgstation.Server.Host.Core var loggingOptions = provider.GetRequiredService>(); fileLoggingConfiguration = loggingOptions.Value; + var controlPanelOptions = provider.GetRequiredService>(); + controlPanelConfiguration = controlPanelOptions.Value; + ioManager = provider.GetRequiredService(); platformIdentifier = provider.GetRequiredService(); } - //setup file logging via serilog + // setup file logging via serilog if (!fileLoggingConfiguration.Disable) services.AddLogging(builder => { - //common app data is C:/ProgramData on windows, else /usr/shar + // common app data is C:/ProgramData on windows, else /usr/shar var logPath = !String.IsNullOrEmpty(fileLoggingConfiguration.Directory) ? fileLoggingConfiguration.Directory : ioManager.ConcatPath(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), VersionPrefix, "Logs"); logPath = ioManager.ConcatPath(logPath, "tgs-{Date}.log"); @@ -175,7 +180,7 @@ namespace Tgstation.Server.Host.Core default: throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid log level {0}", logLevel)); } - }; + } var logEventLevel = ConvertLogLevel(fileLoggingConfiguration.LogLevel); var microsoftEventLevel = ConvertLogLevel(fileLoggingConfiguration.MicrosoftLogLevel); @@ -195,23 +200,24 @@ namespace Tgstation.Server.Host.Core builder.AddSerilog(configuration.CreateLogger(), true); }); - //configure bearer token validation + // configure bearer token validation services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(jwtBearerOptions => { - //this line isn't actually run until the first request is made - //at that point tokenFactory will be populated + // this line isn't actually run until the first request is made + // at that point tokenFactory will be populated jwtBearerOptions.TokenValidationParameters = tokenFactory.ValidationParameters; jwtBearerOptions.Events = new JwtBearerEvents { - //Application is our composition root so this monstrosity of a line is okay + // Application is our composition root so this monstrosity of a line is okay OnTokenValidated = ctx => ctx.HttpContext.RequestServices.GetRequiredService().InjectClaimsIntoContext(ctx, ctx.HttpContext.RequestAborted) }; }); - //fucking converts 'sub' to M$ bs - //can't be done in the above lambda, that's too late + + // fucking prevents converting 'sub' to M$ bs + // can't be done in the above lambda, that's too late JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - //add mvc, configure the json serializer settings + // add mvc, configure the json serializer settings services.AddMvc().AddJsonOptions(options => { options.AllowInputFormatterExceptionMessages = true; @@ -222,6 +228,13 @@ namespace Tgstation.Server.Host.Core options.SerializerSettings.Converters = new[] { new VersionConverter() }; }); + // enable browser detection + services.AddDetectionCore().AddBrowser(); + + // enable CORS if necessary + if (controlPanelConfiguration.AllowAnyOrigin || controlPanelConfiguration.AllowedOrigins?.Count > 0) + services.AddCors(); + void AddTypedContext() where TContext : DatabaseContext { services.AddDbContext(builder => @@ -232,7 +245,7 @@ namespace Tgstation.Server.Host.Core services.AddScoped(x => x.GetRequiredService()); } - //add the correct database context type + // add the correct database context type var dbType = databaseConfiguration.DatabaseType; switch (dbType) { @@ -247,11 +260,11 @@ namespace Tgstation.Server.Host.Core throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid {0}: {1}!", nameof(DatabaseType), dbType)); } - //configure other database services + // configure other database services services.AddSingleton(); services.AddSingleton(); - //configure security services + // configure security services services.AddScoped(); services.AddScoped(); services.AddSingleton(); @@ -259,7 +272,7 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton, PasswordHasher>(); - //configure platform specific services + // configure platform specific services if (platformIdentifier.IsWindows) { services.AddSingleton(); @@ -280,7 +293,7 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); } - //configure misc services + // configure misc services services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -290,14 +303,14 @@ namespace Tgstation.Server.Host.Core SendTimeout = generalConfiguration.ByondTopicTimeout }); - //configure component services + // configure component services services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - //configure root services + // configure root services services.AddSingleton(); services.AddSingleton(x => x.GetRequiredService()); services.AddSingleton(x => x.GetRequiredService()); @@ -311,8 +324,9 @@ namespace Tgstation.Server.Host.Core /// The to configure /// The for the /// The value of + /// The containing the to use /// The for the - public void Configure(IApplicationBuilder applicationBuilder, IServerControl serverControl, ITokenFactory tokenFactory, ILogger logger) + public void Configure(IApplicationBuilder applicationBuilder, IServerControl serverControl, ITokenFactory tokenFactory, IOptions controlPanelConfigurationOptions, ILogger logger) { if (applicationBuilder == null) throw new ArgumentNullException(nameof(applicationBuilder)); @@ -321,43 +335,76 @@ namespace Tgstation.Server.Host.Core this.tokenFactory = tokenFactory ?? throw new ArgumentNullException(nameof(tokenFactory)); + var controlPanelConfiguration = controlPanelConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(controlPanelConfigurationOptions)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); logger.LogInformation(VersionString); + logger.LogDebug("Content Root: {0}", hostingEnvironment.ContentRootPath); + logger.LogTrace("Web Root: {0}", hostingEnvironment.WebRootPath); - //attempt to restart the server if the configuration changes + // attempt to restart the server if the configuration changes if(serverControl.WatchdogPresent) ChangeToken.OnChange(configuration.GetReloadToken, () => serverControl.Restart()); - //now setup the HTTP request pipeline + // setup the HTTP request pipeline - //should anything after this throw an exception, catch it and display a detailed html page - applicationBuilder.UseDeveloperExceptionPage(); //it is not worth it to limit this, you should only ever get it if you're an authorized user - - //suppress OperationCancelledExceptions, they are just aborted HTTP requests + // should anything after this throw an exception, catch it and display a detailed html page + applicationBuilder.UseDeveloperExceptionPage(); // it is not worth it to limit this, you should only ever get it if you're an authorized user + + // suppress OperationCancelledExceptions, they are just aborted HTTP requests applicationBuilder.UseCancelledRequestSuppression(); - //Do not service requests until Ready is called, this will return 503 until that point + // Set up CORS based on configuration if necessary + Action corsBuilder = null; + if (controlPanelConfiguration.AllowAnyOrigin) + { + logger.LogTrace("Access-Control-Allow-Origin: *"); + corsBuilder = builder => builder.AllowAnyOrigin(); + } + else if (controlPanelConfiguration.AllowedOrigins?.Count > 0) + { + logger.LogTrace("Access-Control-Allow-Origin: ", String.Join(',', controlPanelConfiguration.AllowedOrigins)); + corsBuilder = builder => builder.WithOrigins(controlPanelConfiguration.AllowedOrigins.ToArray()); + } + + if (corsBuilder != null) + { + var originalBuilder = corsBuilder; + corsBuilder = builder => originalBuilder(builder.AllowAnyHeader().AllowAnyMethod()); + applicationBuilder.UseCors(corsBuilder); + } + + // Do not service requests until Ready is called, this will return 503 until that point applicationBuilder.UseAsyncInitialization(async cancellationToken => { using (cancellationToken.Register(() => startupTcs.SetCanceled())) await startupTcs.Task.ConfigureAwait(false); }); - //authenticate JWT tokens using our security pipeline if present, returns 401 if bad + // spa loading if necessary + if (controlPanelConfiguration.Enable) + { + logger.LogWarning("Web control panel enabled. This is a highly WIP feature!"); + applicationBuilder.UseStaticFiles(); + } + else + logger.LogDebug("Web control panel disabled!"); + + // authenticate JWT tokens using our security pipeline if present, returns 401 if bad applicationBuilder.UseAuthentication(); - //suppress and log database exceptions + // suppress and log database exceptions applicationBuilder.UseDbConflictHandling(); - //majority of handling is done in the controllers + // majority of handling is done in the controllers applicationBuilder.UseMvc(); - //404 anything that gets this far + // 404 anything that gets this far } - /// + /// public void Ready(Exception initializationError) { lock (startupTcs) diff --git a/src/Tgstation.Server.Host/Core/IJobManager.cs b/src/Tgstation.Server.Host/Core/IJobManager.cs index 12ecf7666f..6599b59a03 100644 --- a/src/Tgstation.Server.Host/Core/IJobManager.cs +++ b/src/Tgstation.Server.Host/Core/IJobManager.cs @@ -12,8 +12,10 @@ namespace Tgstation.Server.Host.Core public interface IJobManager : IHostedService { /// - /// Get the for a job + /// Get the for a /// + /// The to get for + /// The of int? JobProgress(Job job); /// diff --git a/src/Tgstation.Server.Host/Core/IProcessExecutor.cs b/src/Tgstation.Server.Host/Core/IProcessExecutor.cs index 3bd993ada9..57e81ce382 100644 --- a/src/Tgstation.Server.Host/Core/IProcessExecutor.cs +++ b/src/Tgstation.Server.Host/Core/IProcessExecutor.cs @@ -9,8 +9,8 @@ /// Launch a /// /// The full path to the executable file - /// The arguments for the /// The working directory for the + /// The arguments for the /// If standard output should be read /// If standard error should be read /// If shell execute should not be used. Ignored if or are set diff --git a/src/Tgstation.Server.Host/Core/IServerControl.cs b/src/Tgstation.Server.Host/Core/IServerControl.cs index 184280b033..03b5b10a67 100644 --- a/src/Tgstation.Server.Host/Core/IServerControl.cs +++ b/src/Tgstation.Server.Host/Core/IServerControl.cs @@ -36,5 +36,12 @@ namespace Tgstation.Server.Host.Core /// /// A representing the running operation Task Restart(); + + /// + /// Kill the server with a fatal exception + /// + /// The to propagate to the watchdog if any + /// A representing the running operation + Task Die(Exception exception); } } diff --git a/src/Tgstation.Server.Host/Core/ISetupWizard.cs b/src/Tgstation.Server.Host/Core/ISetupWizard.cs index bf8ddae948..587bcf67b7 100644 --- a/src/Tgstation.Server.Host/Core/ISetupWizard.cs +++ b/src/Tgstation.Server.Host/Core/ISetupWizard.cs @@ -11,8 +11,8 @@ namespace Tgstation.Server.Host.Core /// /// Run the setup wizard if necessary /// - /// - /// + /// The for the operation + /// A resulting in if the wizard ran, otherwise Task CheckRunWizard(CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Core/JobHandler.cs b/src/Tgstation.Server.Host/Core/JobHandler.cs index c57c7bdc1f..e09f574581 100644 --- a/src/Tgstation.Server.Host/Core/JobHandler.cs +++ b/src/Tgstation.Server.Host/Core/JobHandler.cs @@ -13,6 +13,7 @@ namespace Tgstation.Server.Host.Core /// The for /// readonly CancellationTokenSource cancellationTokenSource; + /// /// The being run /// @@ -30,7 +31,7 @@ namespace Tgstation.Server.Host.Core task = job(cancellationTokenSource.Token); } - /// + /// public void Dispose() => cancellationTokenSource.Dispose(); /// diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index b3aeac331d..dbbc1eba6d 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -69,14 +69,14 @@ namespace Tgstation.Server.Host.Core /// The for the operation /// A representing the running operation async Task RunJob(Job job, Func operation, CancellationToken cancellationToken) - { + { try { await databaseContextFactory.UseContext(async databaseContext => { async Task HandleExceptions(Task task) { - void LogRegularException() => logger.LogDebug("Job {0} exited with error! Exception: {1}", job.Id, job.ExceptionDetails); + void LogRegularException() => logger.LogDebug("Job {0} exited with error! Exception: {1}", job.Id, job.ExceptionDetails); try { await task.ConfigureAwait(false); @@ -113,7 +113,7 @@ namespace Tgstation.Server.Host.Core await operation(job, databaseContext, cancellationToken).ConfigureAwait(false); logger.LogDebug("Job {0} completed!", job.Id); - }; + } await HandleExceptions(RunJobInternal()).ConfigureAwait(false); @@ -121,7 +121,7 @@ namespace Tgstation.Server.Host.Core bool JobErroredOrCancelled() => job.ExceptionDetails != null || job.Cancelled == true; - //ok so, now it's time for the post commit step if it exists + // ok so, now it's time for the post commit step if it exists if (!JobErroredOrCancelled() && job.PostComplete != null) { await HandleExceptions(job.PostComplete(cancellationToken)).ConfigureAwait(false); @@ -186,7 +186,7 @@ namespace Tgstation.Server.Host.Core logger.LogTrace("Starting job manager..."); await databaseContextFactory.UseContext(async databaseContext => { - //mark all jobs as cancelled + // mark all jobs as cancelled var badJobs = await databaseContext.Jobs.Where(y => !y.StoppedAt.HasValue).Select(y => y.Id).ToListAsync(cancellationToken).ConfigureAwait(false); if (badJobs.Count > 0) { @@ -198,6 +198,7 @@ namespace Tgstation.Server.Host.Core job.Cancelled = true; job.StoppedAt = DateTimeOffset.Now; } + await databaseContext.Save(cancellationToken).ConfigureAwait(false); } }).ConfigureAwait(false); @@ -229,10 +230,11 @@ namespace Tgstation.Server.Host.Core } catch (InvalidOperationException) { - //this is fine + // this is fine return false; } - handler.Cancel(); //this will ensure the db update is only done once + + handler.Cancel(); // this will ensure the db update is only done once await databaseContextFactory.UseContext(async databaseContext => { job = new Job { Id = job.Id }; @@ -240,7 +242,8 @@ namespace Tgstation.Server.Host.Core user = new User { Id = user.Id }; databaseContext.Users.Attach(user); job.CancelledBy = user; - //let either startup or cancellation set job.cancelled + + // let either startup or cancellation set job.cancelled await databaseContext.Save(cancellationToken).ConfigureAwait(false); }).ConfigureAwait(false); if (blocking) @@ -274,6 +277,7 @@ namespace Tgstation.Server.Host.Core if (!jobs.TryGetValue(job.Id, out handler)) return; } + Task cancelTask = null; using (jobCancellationToken.Register(() => cancelTask = CancelJob(job, canceller, true, cancellationToken))) await handler.Wait(cancellationToken).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Core/Process.cs b/src/Tgstation.Server.Host/Core/Process.cs index 813ea77519..21856ed710 100644 --- a/src/Tgstation.Server.Host/Core/Process.cs +++ b/src/Tgstation.Server.Host/Core/Process.cs @@ -28,6 +28,16 @@ namespace Tgstation.Server.Host.Core /// readonly ILogger logger; + /// + /// Construct a + /// + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// If was NOT just created public Process(System.Diagnostics.Process handle, Task lifetime, StringBuilder outputStringBuilder, StringBuilder errorStringBuilder, StringBuilder combinedStringBuilder, ILogger logger, bool preExisting) { this.handle = handle ?? throw new ArgumentNullException(nameof(handle)); @@ -103,6 +113,7 @@ namespace Tgstation.Server.Host.Core } } + /// public void SetHighPriority() { try diff --git a/src/Tgstation.Server.Host/Core/ProcessExecutor.cs b/src/Tgstation.Server.Host/Core/ProcessExecutor.cs index 2786af6c83..7c1d5274eb 100644 --- a/src/Tgstation.Server.Host/Core/ProcessExecutor.cs +++ b/src/Tgstation.Server.Host/Core/ProcessExecutor.cs @@ -38,6 +38,7 @@ namespace Tgstation.Server.Host.Core { return; } + tcs.SetResult(exitCode); }; return tcs.Task; @@ -68,6 +69,7 @@ namespace Tgstation.Server.Host.Core logger.LogDebug("Unable to get process {0}! Exception: {1}", id, e); return null; } + try { return new Process(handle, AttachExitHandler(handle), null, null, null, loggerFactory.CreateLogger(), true); @@ -108,6 +110,7 @@ namespace Tgstation.Server.Host.Core outputStringBuilder.Append(e.Data); }; } + if (readError) { errorStringBuilder = new StringBuilder(); diff --git a/src/Tgstation.Server.Host/Core/SemaphoreSlimContext.cs b/src/Tgstation.Server.Host/Core/SemaphoreSlimContext.cs index d16b7ae628..8c78b4570f 100644 --- a/src/Tgstation.Server.Host/Core/SemaphoreSlimContext.cs +++ b/src/Tgstation.Server.Host/Core/SemaphoreSlimContext.cs @@ -38,10 +38,13 @@ namespace Tgstation.Server.Host.Core /// Construct a /// /// The value of - SemaphoreSlimContext(SemaphoreSlim lockedSemaphore) => this.lockedSemaphore = lockedSemaphore; + SemaphoreSlimContext(SemaphoreSlim lockedSemaphore) + { + this.lockedSemaphore = lockedSemaphore; + } /// - /// Finalize the + /// Finalizes an instance of the class. /// #pragma warning disable CA1821 // Remove empty Finalizers //TODO remove this when https://github.com/dotnet/roslyn-analyzers/issues/1241 is fixed ~SemaphoreSlimContext() => Dispose(); @@ -58,6 +61,7 @@ namespace Tgstation.Server.Host.Core return; disposed = true; } + GC.SuppressFinalize(this); lockedSemaphore.Release(); } diff --git a/src/Tgstation.Server.Host/Core/ServiceCollectionExtensions.cs b/src/Tgstation.Server.Host/Core/ServiceCollectionExtensions.cs new file mode 100644 index 0000000000..cd50b10b88 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/ServiceCollectionExtensions.cs @@ -0,0 +1,44 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Globalization; +using Tgstation.Server.Host.Configuration; + +namespace Tgstation.Server.Host.Core +{ + /// + /// Extensions for + /// + static class ServiceCollectionExtensions + { + /// + /// Add a standard binding + /// + /// The to bind. Must have a const/static field named "Section" + /// The to configure + /// The containing the + /// + public static IServiceCollection UseStandardConfig(this IServiceCollection serviceCollection, IConfiguration configuration) where TConfig : class + { + if (serviceCollection == null) + throw new ArgumentNullException(nameof(serviceCollection)); + if (configuration == null) + throw new ArgumentNullException(nameof(configuration)); + + const string SectionFieldName = nameof(GeneralConfiguration.Section); + + var configType = typeof(TConfig); + var sectionField = configType.GetField(SectionFieldName); + if (sectionField == null) + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "{0} has no {1} field!", configType, SectionFieldName)); + + var stringType = typeof(string); + if (sectionField.FieldType != stringType) + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "{0} has invalid {1} field type, must be {2}!", configType, SectionFieldName, stringType)); + + var sectionName = (string)sectionField.GetValue(null); + + return serviceCollection.Configure(configuration.GetSection(sectionName)); + } + } +} diff --git a/src/Tgstation.Server.Host/Core/SetupWizard.cs b/src/Tgstation.Server.Host/Core/SetupWizard.cs index 83b6456fe3..44fc7eb3ff 100644 --- a/src/Tgstation.Server.Host/Core/SetupWizard.cs +++ b/src/Tgstation.Server.Host/Core/SetupWizard.cs @@ -8,7 +8,7 @@ using System.Collections.Generic; using System.Data.Common; using System.Data.SqlClient; using System.Globalization; -using System.Runtime.InteropServices; +using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -158,6 +158,7 @@ namespace Tgstation.Server.Host.Core databaseConfiguration.DatabaseType = databaseType; break; } + await console.WriteAsync("Invalid database type!", true, cancellationToken).ConfigureAwait(false); } while (true); @@ -171,7 +172,6 @@ namespace Tgstation.Server.Host.Core await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false); await console.WriteAsync("Enter the database name (Can be from previous installation. Otherwise, should not exist): ", false, cancellationToken).ConfigureAwait(false); string databaseName; - do { databaseName = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false); @@ -206,6 +206,7 @@ namespace Tgstation.Server.Host.Core await console.WriteAsync("The account it uses in MSSQL is usually \"NT AUTHORITY\\SYSTEM\" and the role it needs is usually \"dbcreator\".", true, cancellationToken).ConfigureAwait(false); await console.WriteAsync("We'll run a sanity test here, but it won't be indicative of the service's permissions if that is the case", true, cancellationToken).ConfigureAwait(false); } + await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false); DbConnection testConnection; @@ -261,7 +262,7 @@ namespace Tgstation.Server.Host.Core using (var command = testConnection.CreateCommand()) { command.CommandText = "SELECT VERSION()"; - var fullVersion = (string)(await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false)); + var fullVersion = (string)await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false); await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Found {0}", fullVersion), true, cancellationToken).ConfigureAwait(false); var splits = fullVersion.Split('-'); databaseConfiguration.MySqlServerVersion = splits[0]; @@ -276,6 +277,7 @@ namespace Tgstation.Server.Host.Core command.CommandText = String.Format(CultureInfo.InvariantCulture, "CREATE DATABASE {0}", databaseName); await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false); } + await console.WriteAsync("Success!", true, cancellationToken).ConfigureAwait(false); await console.WriteAsync("Dropping test database...", true, cancellationToken).ConfigureAwait(false); using (var command = testConnection.CreateCommand()) @@ -313,7 +315,8 @@ namespace Tgstation.Server.Host.Core await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false); await console.WriteAsync("Retrying database configuration...", true, cancellationToken).ConfigureAwait(false); } - } while (true); + } + while (true); } /// @@ -327,6 +330,7 @@ namespace Tgstation.Server.Host.Core { SetupWizardMode = SetupWizardMode.Never }; + do { await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false); @@ -339,6 +343,7 @@ namespace Tgstation.Server.Host.Core newGeneralConfiguration.MinimumPasswordLength = passwordLength; break; } + await console.WriteAsync("Please enter a positive integer!", true, cancellationToken).ConfigureAwait(false); } while (true); @@ -355,6 +360,7 @@ namespace Tgstation.Server.Host.Core newGeneralConfiguration.ByondTopicTimeout = topicTimeout; break; } + await console.WriteAsync("Please enter a positive integer!", true, cancellationToken).ConfigureAwait(false); } while (true); @@ -390,7 +396,8 @@ namespace Tgstation.Server.Host.Core fileLoggingConfiguration.Directory = null; break; } - //test a write of it + + // test a write of it await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false); await console.WriteAsync("Testing directory access...", true, cancellationToken).ConfigureAwait(false); try @@ -412,6 +419,7 @@ namespace Tgstation.Server.Host.Core await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false); await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false); } + break; } catch (OperationCanceledException) @@ -424,7 +432,8 @@ namespace Tgstation.Server.Host.Core await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false); await console.WriteAsync("Please verify the path is valid and you have access to it!", true, cancellationToken).ConfigureAwait(false); } - } while (true); + } + while (true); async Task PromptLogLevel(string question) { @@ -432,22 +441,51 @@ namespace Tgstation.Server.Host.Core { await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false); await console.WriteAsync(question, true, cancellationToken).ConfigureAwait(false); - await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Enter one of {0}/{1}/{2}/{3}/{4}/{5} (leave blank for default): ",nameof(LogLevel.Trace), nameof(LogLevel.Debug), nameof(LogLevel.Information), nameof(LogLevel.Warning), nameof(LogLevel.Error), nameof(LogLevel.Critical)), false, cancellationToken).ConfigureAwait(false); + await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Enter one of {0}/{1}/{2}/{3}/{4}/{5} (leave blank for default): ", nameof(LogLevel.Trace), nameof(LogLevel.Debug), nameof(LogLevel.Information), nameof(LogLevel.Warning), nameof(LogLevel.Error), nameof(LogLevel.Critical)), false, cancellationToken).ConfigureAwait(false); var responseString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false); if (String.IsNullOrWhiteSpace(responseString)) return null; if (Enum.TryParse(responseString, out var logLevel) && logLevel != LogLevel.None) return logLevel; await console.WriteAsync("Invalid log level!", true, cancellationToken).ConfigureAwait(false); - } while (true); + } + while (true); } fileLoggingConfiguration.LogLevel = await PromptLogLevel(String.Format(CultureInfo.InvariantCulture, "Enter the level limit for normal logs (default {0}).", fileLoggingConfiguration.LogLevel)).ConfigureAwait(false) ?? fileLoggingConfiguration.LogLevel; fileLoggingConfiguration.MicrosoftLogLevel = await PromptLogLevel(String.Format(CultureInfo.InvariantCulture, "Enter the level limit for Microsoft logs (VERY verbose, default {0}).", fileLoggingConfiguration.MicrosoftLogLevel)).ConfigureAwait(false) ?? fileLoggingConfiguration.MicrosoftLogLevel; } + return fileLoggingConfiguration; } + /// + /// Prompts the user to create a + /// + /// The for the operation + /// A resulting in the new + async Task ConfigureControlPanel(CancellationToken cancellationToken) + { + var config = new ControlPanelConfiguration + { + Enable = await PromptYesNo("Enable the web control panel? (y/n): ", cancellationToken).ConfigureAwait(false), + AllowAnyOrigin = await PromptYesNo("Allow web control panels hosted elsewhere to access the server? (Access-Control-Allow-Origin: *) (y/n): ", cancellationToken).ConfigureAwait(false) + }; + + if (!config.AllowAnyOrigin) + { + await console.WriteAsync("Enter a comma seperated list of CORS allowed origins (optional): ", false, cancellationToken).ConfigureAwait(false); + var commaSeperatedOrigins = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false); + if (!String.IsNullOrWhiteSpace(commaSeperatedOrigins)) + { + var splits = commaSeperatedOrigins.Split(','); + config.AllowedOrigins = new List(splits.Select(x => x.Trim())); + } + } + + return config; + } + /// /// Saves a given set to /// @@ -456,9 +494,10 @@ namespace Tgstation.Server.Host.Core /// The to save /// The to save /// The to save + /// The to save /// The for the operation /// A representing the running operation - async Task SaveConfiguration(string userConfigFileName, ushort? hostingPort, DatabaseConfiguration databaseConfiguration, GeneralConfiguration newGeneralConfiguration, FileLoggingConfiguration fileLoggingConfiguration, CancellationToken cancellationToken) + async Task SaveConfiguration(string userConfigFileName, ushort? hostingPort, DatabaseConfiguration databaseConfiguration, GeneralConfiguration newGeneralConfiguration, FileLoggingConfiguration fileLoggingConfiguration, ControlPanelConfiguration controlPanelConfiguration, CancellationToken cancellationToken) { await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Configuration complete! Saving to {0}", userConfigFileName), true, cancellationToken).ConfigureAwait(false); @@ -466,7 +505,8 @@ namespace Tgstation.Server.Host.Core { { DatabaseConfiguration.Section, databaseConfiguration }, { GeneralConfiguration.Section, newGeneralConfiguration }, - { FileLoggingConfiguration.Section, fileLoggingConfiguration } + { FileLoggingConfiguration.Section, fileLoggingConfiguration }, + { ControlPanelConfiguration.Section, controlPanelConfiguration } }; if (hostingPort.HasValue) @@ -507,7 +547,7 @@ namespace Tgstation.Server.Host.Core await console.WriteAsync("Waiting for configuration changes to reload...", true, cancellationToken).ConfigureAwait(false); - //we need to wait for the configuration's file system watcher to read and reload the changes + // we need to wait for the configuration's file system watcher to read and reload the changes await asyncDelayer.Delay(TimeSpan.FromSeconds(5), cancellationToken).ConfigureAwait(false); } @@ -519,7 +559,7 @@ namespace Tgstation.Server.Host.Core /// A representing the running operation async Task RunWizard(string userConfigFileName, CancellationToken cancellationToken) { - //welcome message + // welcome message await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false); await console.WriteAsync("Welcome to tgstation-server 4!", true, cancellationToken).ConfigureAwait(false); await console.WriteAsync("This wizard will help you configure your server.", true, cancellationToken).ConfigureAwait(false); @@ -532,9 +572,11 @@ namespace Tgstation.Server.Host.Core var fileLoggingConfiguration = await ConfigureLogging(cancellationToken).ConfigureAwait(false); + var controlPanelConfiguration = await ConfigureControlPanel(cancellationToken).ConfigureAwait(false); + await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false); - await SaveConfiguration(userConfigFileName, hostingPort, databaseConfiguration, newGeneralConfiguration, fileLoggingConfiguration, cancellationToken).ConfigureAwait(false); + await SaveConfiguration(userConfigFileName, hostingPort, databaseConfiguration, newGeneralConfiguration, fileLoggingConfiguration, controlPanelConfiguration, cancellationToken).ConfigureAwait(false); } /// @@ -566,7 +608,7 @@ namespace Tgstation.Server.Host.Core { var bytes = await ioManager.ReadAllBytes(userConfigFileName, cancellationToken).ConfigureAwait(false); var contents = Encoding.UTF8.GetString(bytes); - var existingConfigIsEmpty = String.IsNullOrWhiteSpace(contents); + var existingConfigIsEmpty = String.IsNullOrWhiteSpace(contents) || contents.Trim() == "{}"; logger.LogTrace("Configuration json detected. Empty: {0}", existingConfigIsEmpty); shouldRunBasedOnAutodetect = existingConfigIsEmpty; } @@ -576,7 +618,6 @@ namespace Tgstation.Server.Host.Core logger.LogTrace("No configuration json detected"); } - if (!shouldRunBasedOnAutodetect) { if (forceRun) @@ -586,11 +627,12 @@ namespace Tgstation.Server.Host.Core forceRun = await PromptYesNo("Continue running setup wizard? (y/n): ", cancellationToken).ConfigureAwait(false); } - if (!forceRun) + + if (!forceRun) return false; } - //flush the logs to prevent console conflicts + // flush the logs to prevent console conflicts await asyncDelayer.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); await RunWizard(userConfigFileName, cancellationToken).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/IO/Console.cs b/src/Tgstation.Server.Host/IO/Console.cs index 35d3b1e135..9f1fceba80 100644 --- a/src/Tgstation.Server.Host/IO/Console.cs +++ b/src/Tgstation.Server.Host/IO/Console.cs @@ -27,7 +27,7 @@ namespace Tgstation.Server.Host.IO /// public Task ReadLineAsync(bool usePasswordChar, CancellationToken cancellationToken) => Task.Factory.StartNew(() => { - //TODO Make this better: https://stackoverflow.com/questions/9479573/how-to-interrupt-console-readline + // TODO: Make this better: https://stackoverflow.com/questions/9479573/how-to-interrupt-console-readline CheckAvailable(); if (!usePasswordChar) return System.Console.ReadLine(); @@ -46,13 +46,15 @@ namespace Tgstation.Server.Host.IO System.Console.Write("\b \b"); } } - else if (keyDescription.KeyChar != '\u0000') // KeyChar == '\u0000' if the key pressed does not correspond to a printable character, e.g. F1, Pause-Break, etc + else if (keyDescription.KeyChar != '\u0000') { + // KeyChar == '\u0000' if the key pressed does not correspond to a printable character, e.g. F1, Pause-Break, etc passwordBuilder.Append(keyDescription.KeyChar); System.Console.Write('*'); } } while (!cancellationToken.IsCancellationRequested); + cancellationToken.ThrowIfCancellationRequested(); System.Console.WriteLine(); return passwordBuilder.ToString(); diff --git a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs index d6cae80fde..c303d0a008 100644 --- a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs @@ -5,7 +5,6 @@ using System.IO; using System.IO.Compression; using System.Linq; using System.Net; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -34,18 +33,21 @@ namespace Tgstation.Server.Host.IO foreach (var subDir in dir.EnumerateDirectories()) { cancellationToken.ThrowIfCancellationRequested(); + + // if below succeeds this is probably a symlink if (!subDir.Attributes.HasFlag(FileAttributes.Directory) || subDir.Attributes.HasFlag(FileAttributes.ReparsePoint)) - //this is probably a symlink subDir.Delete(); else tasks.Add(NormalizeAndDelete(subDir, cancellationToken)); } + foreach (var file in dir.EnumerateFiles()) { cancellationToken.ThrowIfCancellationRequested(); file.Attributes = FileAttributes.Normal; file.Delete(); } + await Task.WhenAll(tasks).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); dir.Delete(true); @@ -84,19 +86,19 @@ namespace Tgstation.Server.Host.IO async Task CopyThisDirectory() { if (!atLeastOneSubDir) - await CreateDirectory(dest, cancellationToken).ConfigureAwait(false); //save on createdir calls + await CreateDirectory(dest, cancellationToken).ConfigureAwait(false); // save on createdir calls var tasks = new List(); - await dir.EnumerateFiles().ToAsyncEnumerable().ForEachAsync(I => + await dir.EnumerateFiles().ToAsyncEnumerable().ForEachAsync(fileInfo => { - if (ignore != null && ignore.Contains(I.Name)) + if (ignore != null && ignore.Contains(fileInfo.Name)) return; - tasks.Add(CopyFile(I.FullName, Path.Combine(dest, I.Name), cancellationToken)); + tasks.Add(CopyFile(fileInfo.FullName, Path.Combine(dest, fileInfo.Name), cancellationToken)); }).ConfigureAwait(false); await Task.WhenAll(tasks).ConfigureAwait(false); - }; + } yield return CopyThisDirectory(); } @@ -115,18 +117,6 @@ namespace Tgstation.Server.Host.IO await directoryCopy.ConfigureAwait(false); } - /// - public async Task AppendAllText(string path, string additional_contents, CancellationToken cancellationToken) - { - if (additional_contents == null) - throw new ArgumentNullException(nameof(additional_contents)); - using (var destStream = new FileStream(ResolvePath(path), FileMode.Append, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete, DefaultBufferSize, true)) - { - var buf = Encoding.UTF8.GetBytes(additional_contents); - await destStream.WriteAsync(buf, 0, buf.Length, cancellationToken).ConfigureAwait(false); - } - } - /// public string ConcatPath(params string[] paths) { @@ -190,6 +180,7 @@ namespace Tgstation.Server.Host.IO cancellationToken.ThrowIfCancellationRequested(); results.Add(I); } + return results; }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); @@ -248,6 +239,7 @@ namespace Tgstation.Server.Host.IO results.Add(I); cancellationToken.ThrowIfCancellationRequested(); } + return (IReadOnlyList)results; }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); @@ -262,21 +254,14 @@ namespace Tgstation.Server.Host.IO results.Add(I); cancellationToken.ThrowIfCancellationRequested(); } + return (IReadOnlyList)results; }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); - /// - public Task CreateSymlink(string target, string link, CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - target = ResolvePath(target); - link = ResolvePath(link); - - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); - /// public async Task DownloadFile(Uri url, CancellationToken cancellationToken) { - //DownloadDataTaskAsync can't be cancelled and is shittily written, don't use it + // DownloadDataTaskAsync can't be cancelled and is shittily written, don't use it using (var wc = new WebClient()) { var tcs = new TaskCompletionSource(); diff --git a/src/Tgstation.Server.Host/IO/IPostWriteHandler.cs b/src/Tgstation.Server.Host/IO/IPostWriteHandler.cs index 7686d742c8..f8c0f4042a 100644 --- a/src/Tgstation.Server.Host/IO/IPostWriteHandler.cs +++ b/src/Tgstation.Server.Host/IO/IPostWriteHandler.cs @@ -1,5 +1,8 @@ namespace Tgstation.Server.Host.IO { + /// + /// Handles changing file modes/permissions after writing + /// interface IPostWriteHandler { /// diff --git a/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs index 5b457fd840..69f80050b5 100644 --- a/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs @@ -47,7 +47,7 @@ namespace Tgstation.Server.Host.IO bool DeleteDirectory(string path); /// - /// Write to a file at a given . + /// Write to a file at a given /// /// The path to the file to write /// The new contents of the file diff --git a/src/Tgstation.Server.Host/IO/PosixPostWriteHandler.cs b/src/Tgstation.Server.Host/IO/PosixPostWriteHandler.cs index 75eaa24bcd..2c9b6b74cf 100644 --- a/src/Tgstation.Server.Host/IO/PosixPostWriteHandler.cs +++ b/src/Tgstation.Server.Host/IO/PosixPostWriteHandler.cs @@ -15,7 +15,7 @@ namespace Tgstation.Server.Host.IO if (filePath == null) throw new ArgumentNullException(nameof(filePath)); - //set executable bit every time, don't want people calling me when their uploaded "sl" binary doesn't work + // set executable bit every time, don't want people calling me when their uploaded "sl" binary doesn't work if (Syscall.stat(filePath, out var stat) != 0) throw new UnixIOException(Stdlib.GetLastError()); diff --git a/src/Tgstation.Server.Host/IO/ResolvingIOManager.cs b/src/Tgstation.Server.Host/IO/ResolvingIOManager.cs index b00ee71b98..e06b5d98d3 100644 --- a/src/Tgstation.Server.Host/IO/ResolvingIOManager.cs +++ b/src/Tgstation.Server.Host/IO/ResolvingIOManager.cs @@ -17,15 +17,15 @@ namespace Tgstation.Server.Host.IO /// Construct a /// /// The that resolves to the directory to work out of - /// The value of - public ResolvingIOManager(IIOManager parent, string _subdirectory) + /// The value of + public ResolvingIOManager(IIOManager parent, string subdirectory) { if (parent == null) throw new ArgumentNullException(nameof(parent)); - if (_subdirectory == null) - throw new ArgumentNullException(nameof(_subdirectory)); + if (subdirectory == null) + throw new ArgumentNullException(nameof(subdirectory)); - subdirectory = ConcatPath(parent.ResolvePath("."), _subdirectory); + this.subdirectory = ConcatPath(parent.ResolvePath("."), subdirectory); } /// diff --git a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs index c3937d5a9c..2a6530faa0 100644 --- a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs @@ -86,8 +86,8 @@ namespace Tgstation.Server.Host.IO { cancellationToken.ThrowIfCancellationRequested(); - //as nice as it would be to not have to arrayify the memory stream, we have to - //because, oddly enough sha1(memorystream) != sha1(memorystream.ToArray()) + // as nice as it would be to not have to arrayify the memory stream, we have to + // because, oddly enough sha1(memorystream) != sha1(memorystream.ToArray()) // vOv byte[] originalBytes; using (var readMs = new MemoryStream()) @@ -95,24 +95,27 @@ namespace Tgstation.Server.Host.IO file.CopyTo(readMs); originalBytes = readMs.ToArray(); } + + // no sha1? no write if (originalBytes.Length != 0 && sha1InOut == null) - //no sha1? no write return false; - //suppressed due to only using for consistency checks + // suppressed due to only using for consistency checks #pragma warning disable CA5350 // Do not use insecure cryptographic algorithm SHA1. using (var sha1 = new SHA1Managed()) #pragma warning restore CA5350 // Do not use insecure cryptographic algorithm SHA1. { - string GetSha1(byte[] dataToHash) => dataToHash != null && dataToHash.Length != 0 ? String.Join("", sha1.ComputeHash(dataToHash).Select(b => b.ToString("x2", CultureInfo.InvariantCulture))) : null; + string GetSha1(byte[] dataToHash) => dataToHash != null && dataToHash.Length != 0 ? String.Join(String.Empty, sha1.ComputeHash(dataToHash).Select(b => b.ToString("x2", CultureInfo.InvariantCulture))) : null; var originalSha1 = GetSha1(originalBytes); if (originalSha1 != sha1InOut) { sha1InOut = originalSha1; return false; } + sha1InOut = GetSha1(data); } + cancellationToken.ThrowIfCancellationRequested(); if (data != null) @@ -124,6 +127,7 @@ namespace Tgstation.Server.Host.IO file.Write(data, 0, data.Length); } } + if (data == null) File.Delete(path); return true; diff --git a/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs b/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs index 761d37a574..b3a22fdaf0 100644 --- a/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs +++ b/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs @@ -19,17 +19,20 @@ namespace Tgstation.Server.Host.IO if (linkPath == null) throw new ArgumentNullException(nameof(linkPath)); - //check if its not a file + // check if its not a file var flags = File.Exists(targetPath) ? NativeMethods.CreateSymbolicLinkFlags.None : NativeMethods.CreateSymbolicLinkFlags.Directory; - //no don't fucking use this - //sure it works in SOME cases - //i.e. win10 1803+ and IN DEVELOPER MODE - //other times it throws ERROR_INVALID_PARAMETER - //but the fucking worst is there is some configuration of windows that accept the argument and allow the function to succeed - //BUT IT DOESN'T CREATE THE FUCKING LINK - //I AM NOT DEBUGGING THAT SHIT AGAIN AHHH - //flags |= NativeMethods.CreateSymbolicLinkFlags.AllowUnprivilegedCreate; + /* + * no don't fucking use this + * sure it works in SOME cases + * i.e. win10 1803+ and IN DEVELOPER MODE + * other times it throws ERROR_INVALID_PARAMETER + * but the fucking worst is there is some configuration of windows that accept the argument and allow the function to succeed + * BUT IT DOESN'T CREATE THE FUCKING LINK + * I AM NOT DEBUGGING THAT SHIT AGAIN AHHH + + flags |= NativeMethods.CreateSymbolicLinkFlags.AllowUnprivilegedCreate; + */ cancellationToken.ThrowIfCancellationRequested(); if (!NativeMethods.CreateSymbolicLink(linkPath, targetPath, flags)) diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 7578c21c8a..439bbe9161 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -1,7 +1,11 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -10,6 +14,7 @@ using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Models { /// + #pragma warning disable CA1506 // TODO: Decomplexify abstract class DatabaseContext : DbContext, IDatabaseContext where TParentContext : DbContext { /// @@ -169,5 +174,51 @@ namespace Tgstation.Server.Host.Models /// public Task Save(CancellationToken cancellationToken) => SaveChangesAsync(cancellationToken); + + /// + /// If the MY_ class of migrations should be used instead of the MS_ class + /// + /// if the MY_ class of migrations should be used instead of the MS_ class, otherwise + protected abstract bool UseMySQLMigrations(); + + /// + public async Task SchemaDowngradeForServerVersion(Version version, CancellationToken cancellationToken) + { + if (version == null) + throw new ArgumentNullException(nameof(version)); + if (version < new Version(4, 0)) + throw new ArgumentOutOfRangeException(nameof(version), version, "Not a valid V4 version!"); + + string targetMigration = null; + + // Update this with new migrations as they are made + // Always use the MS class + + // TODO: Uncomment once #816 is merged + /* + if (version < new Version(4, 0, 2)) + targetMigration = nameof(MSReattachCompileJobRequired); + */ + + if (targetMigration == null) + return; + + if (UseMySQLMigrations()) + targetMigration = String.Format(CultureInfo.InvariantCulture, "MY" + targetMigration.Substring(2)); + + // even though it clearly implements it in the DatabaseFacade definition this won't work without casting (╯ಠ益ಠ)╯︵ ┻━┻ + var dbServiceProvider = ((IInfrastructure)Database).Instance; + var migrator = dbServiceProvider.GetRequiredService(); + + Logger.LogInformation("Migrating down to version {0}. Target: {1}", version, targetMigration); + try + { + await migrator.MigrateAsync(targetMigration, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) + { + Logger.LogCritical("Failed to migrate! Exception: {0}", e); + } + } } } diff --git a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs index 8fdb8aad74..e7c8e9dcd2 100644 --- a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using System; using System.Threading; using System.Threading.Tasks; @@ -87,5 +88,13 @@ namespace Tgstation.Server.Host.Models /// The for the operation /// A representing the running operation Task Initialize(CancellationToken cancellationToken); + + /// + /// Attempt to downgrade the schema to the migration used for a given server + /// + /// The tgstation-server that the schema should downgrade for + /// The for the operation + /// A representing the running operation + Task SchemaDowngradeForServerVersion(Version version, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Models/Instance.cs b/src/Tgstation.Server.Host/Models/Instance.cs index d7a1c285c3..eaf2c50bde 100644 --- a/src/Tgstation.Server.Host/Models/Instance.cs +++ b/src/Tgstation.Server.Host/Models/Instance.cs @@ -7,7 +7,6 @@ namespace Tgstation.Server.Host.Models /// public sealed class Instance : Api.Models.Instance { - /// /// The for the /// @@ -34,7 +33,7 @@ namespace Tgstation.Server.Host.Models public List InstanceUsers { get; set; } /// - /// The s for the + /// The s for the /// public List ChatSettings { get; set; } diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.cs b/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.cs index 57f363fa2a..c796a12444 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.cs @@ -7,6 +7,7 @@ namespace Tgstation.Server.Host.Models.Migrations /// /// The initial database migration for MSSQL /// + #pragma warning disable CA1506 public partial class MSInitialCreate : Migration { /// diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.cs b/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.cs index 02e3d313d1..2da9d98878 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.cs @@ -7,6 +7,7 @@ namespace Tgstation.Server.Host.Models.Migrations /// /// The initial database migration for MySQL/MariaDB /// + #pragma warning disable CA1506 public partial class MYInitialCreate : Migration { /// diff --git a/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs b/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs index b4b4d13ef2..98af100f86 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs @@ -15,12 +15,16 @@ namespace Tgstation.Server.Host.Models.Migrations /// Path to the json file to use for migrations configuration /// const string RootJson = "appsettings.json"; + /// /// Path to the development json file to use for migrations configuration /// const string DevJson = "appsettings.Development.json"; - /// + /// + /// Get the for the + /// + /// The for the public static IOptions GetDbContextOptions() { var builder = new ConfigurationBuilder(); diff --git a/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs b/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs index 8c03b31ee0..718fc04d98 100644 --- a/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs @@ -38,5 +38,8 @@ namespace Tgstation.Server.Host.Models else options.UseMySql(DatabaseConfiguration.ConnectionString); } + + /// + protected override bool UseMySQLMigrations() => true; } } diff --git a/src/Tgstation.Server.Host/Models/RepositorySettings.cs b/src/Tgstation.Server.Host/Models/RepositorySettings.cs index d4c5df7003..5493ae0fbe 100644 --- a/src/Tgstation.Server.Host/Models/RepositorySettings.cs +++ b/src/Tgstation.Server.Host/Models/RepositorySettings.cs @@ -1,5 +1,4 @@ -using System; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Models @@ -26,10 +25,10 @@ namespace Tgstation.Server.Host.Models /// /// Convert the to it's API form /// - /// A new + /// A new public Repository ToApi() => new Repository { - //AccessToken = AccessToken, //never show this + // AccessToken = AccessToken, // never show this AccessUser = AccessUser, AutoUpdatesKeepTestMerges = AutoUpdatesKeepTestMerges, AutoUpdatesSynchronize = AutoUpdatesSynchronize, @@ -38,7 +37,8 @@ namespace Tgstation.Server.Host.Models PushTestMergeCommits = PushTestMergeCommits, ShowTestMergeCommitters = ShowTestMergeCommitters, PostTestMergeComment = PostTestMergeComment - //revision information and the rest retrieved by controller + + // revision information and the rest retrieved by controller }; } } diff --git a/src/Tgstation.Server.Host/Models/RevisionInformation.cs b/src/Tgstation.Server.Host/Models/RevisionInformation.cs index 896615150f..654f55ef1b 100644 --- a/src/Tgstation.Server.Host/Models/RevisionInformation.cs +++ b/src/Tgstation.Server.Host/Models/RevisionInformation.cs @@ -45,7 +45,7 @@ namespace Tgstation.Server.Host.Models ActiveTestMerges = ActiveTestMerges.Select(x => x.TestMerge.ToApi()).ToList(), CompileJobs = CompileJobs.Select(x => new Api.Models.CompileJob { - Id = x.Id //anti recursion measure + Id = x.Id // anti recursion measure }).ToList() }; } diff --git a/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs b/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs index d3d5aa8260..94240566d3 100644 --- a/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs @@ -26,5 +26,8 @@ namespace Tgstation.Server.Host.Models base.OnConfiguring(options); options.UseSqlServer(DatabaseConfiguration.ConnectionString); } + + /// + protected override bool UseMySQLMigrations() => false; } } diff --git a/src/Tgstation.Server.Host/Models/User.cs b/src/Tgstation.Server.Host/Models/User.cs index c8682b4e7f..bcbe8c23bf 100644 --- a/src/Tgstation.Server.Host/Models/User.cs +++ b/src/Tgstation.Server.Host/Models/User.cs @@ -62,7 +62,7 @@ namespace Tgstation.Server.Host.Models }; /// - /// Convert the to it's API form + /// Convert the to it's API form /// /// If rights and system identifier should be shown /// A new diff --git a/src/Tgstation.Server.Host/NativeMethods.cs b/src/Tgstation.Server.Host/NativeMethods.cs index bcc1865686..3686e79f9e 100644 --- a/src/Tgstation.Server.Host/NativeMethods.cs +++ b/src/Tgstation.Server.Host/NativeMethods.cs @@ -7,6 +7,10 @@ namespace Tgstation.Server.Host /// /// Native methods used by the code /// + #pragma warning disable SA1600 + #pragma warning disable SA1602 + #pragma warning disable SA1611 + #pragma warning disable SA1615 static class NativeMethods { /// diff --git a/src/Tgstation.Server.Host/Program.cs b/src/Tgstation.Server.Host/Program.cs index d7c28a061f..83895df62b 100644 --- a/src/Tgstation.Server.Host/Program.cs +++ b/src/Tgstation.Server.Host/Program.cs @@ -15,7 +15,9 @@ namespace Tgstation.Server.Host /// /// The to use /// - internal static IServerFactory serverFactory = new ServerFactory(); +#pragma warning disable SA1401 // Fields must be private + internal static IServerFactory ServerFactory = new ServerFactory(); +#pragma warning restore SA1401 // Fields must be private /// /// Entrypoint for the @@ -25,7 +27,8 @@ namespace Tgstation.Server.Host public static async Task Main(string[] args) { var listArgs = new List(args); - //first arg is 100% always the update path, starting it otherwise is solely for debugging purposes + + // first arg is 100% always the update path, starting it otherwise is solely for debugging purposes string updatePath; if (listArgs.Count > 0) { @@ -38,7 +41,7 @@ namespace Tgstation.Server.Host updatePath = null; try { - var server = serverFactory.CreateServer(listArgs.ToArray(), updatePath); + var server = ServerFactory.CreateServer(listArgs.ToArray(), updatePath); try { using (var cts = new CancellationTokenSource()) @@ -70,6 +73,7 @@ namespace Tgstation.Server.Host File.WriteAllText(updatePath, e.ToString()); return 2; } + throw; } } diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContext.cs b/src/Tgstation.Server.Host/Security/AuthenticationContext.cs index 1ef6364651..b621a41c04 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContext.cs @@ -53,6 +53,7 @@ namespace Tgstation.Server.Host.Security if (isInstance && InstanceUser == null) return 0; var rightsEnum = RightsHelper.RightToType(rightsType); + // use the api versions because they're the ones that contain the actual properties var typeToCheck = isInstance ? typeof(InstanceUser) : typeof(User); diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs index 087a6d33dc..bfe70be61d 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs @@ -76,6 +76,7 @@ namespace Tgstation.Server.Host.Security CurrentAuthenticationContext = new AuthenticationContext(); return; } + systemIdentity = null; } diff --git a/src/Tgstation.Server.Host/Security/ClaimsInjector.cs b/src/Tgstation.Server.Host/Security/ClaimsInjector.cs index 3e7f202b2e..c3b67cb489 100644 --- a/src/Tgstation.Server.Host/Security/ClaimsInjector.cs +++ b/src/Tgstation.Server.Host/Security/ClaimsInjector.cs @@ -43,7 +43,7 @@ namespace Tgstation.Server.Host.Security if (tokenValidatedContext == null) throw new ArgumentNullException(nameof(tokenValidatedContext)); - //Find the user id in the token + // Find the user id in the token var userIdClaim = tokenValidatedContext.Principal.FindFirst(JwtRegisteredClaimNames.Sub); if (userIdClaim == default) throw new InvalidOperationException("Missing required claim!"); @@ -65,11 +65,11 @@ namespace Tgstation.Server.Host.Security } catch (InvalidOperationException) { - //we are not responsible for handling header validation issues + // we are not responsible for handling header validation issues return; } - //This populates the CurrentAuthenticationContext field for use by us and subsequent controllers + // This populates the CurrentAuthenticationContext field for use by us and subsequent controllers await authenticationContextFactory.CreateAuthenticationContext(userId, apiHeaders.InstanceId, tokenValidatedContext.SecurityToken.ValidFrom, cancellationToken).ConfigureAwait(false); var authenticationContext = authenticationContextFactory.CurrentAuthenticationContext; @@ -78,9 +78,9 @@ namespace Tgstation.Server.Host.Security var claims = new List(); foreach (RightsType I in enumerator) { - //if there's no instance user, do a weird thing and add all the instance roles - //we need it so we can get to OnActionExecutionAsync where we can properly decide between BadRequest and Forbid - //if user is null that means they got the token with an expired password + // if there's no instance user, do a weird thing and add all the instance roles + // we need it so we can get to OnActionExecutionAsync where we can properly decide between BadRequest and Forbid + // if user is null that means they got the token with an expired password var rightInt = authenticationContext.User == null || (RightsHelper.IsInstanceRight(I) && authenticationContext.InstanceUser == null) ? ~0U : authenticationContext.GetRight(I); var rightEnum = RightsHelper.RightToType(I); var right = (Enum)Enum.ToObject(rightEnum, rightInt); diff --git a/src/Tgstation.Server.Host/Security/CryptographySuite.cs b/src/Tgstation.Server.Host/Security/CryptographySuite.cs index 4cdd6be43a..77ab55db94 100644 --- a/src/Tgstation.Server.Host/Security/CryptographySuite.cs +++ b/src/Tgstation.Server.Host/Security/CryptographySuite.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Identity; using System; +using System.Globalization; using System.Security.Cryptography; using Tgstation.Server.Host.Models; @@ -53,14 +54,20 @@ namespace Tgstation.Server.Host.Security /// public bool CheckUserPassword(User user, string password) { - switch (passwordHasher.VerifyHashedPassword(user, user.PasswordHash, password)) + var result = passwordHasher.VerifyHashedPassword(user, user.PasswordHash, password); + switch (result) { case PasswordVerificationResult.Failed: return false; case PasswordVerificationResult.SuccessRehashNeeded: SetUserPassword(user, password, false); break; + case PasswordVerificationResult.Success: + break; + default: + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Password hasher return unknown PasswordVerificationResult: {0}", result)); } + return true; } diff --git a/src/Tgstation.Server.Host/Security/IdentityCache.cs b/src/Tgstation.Server.Host/Security/IdentityCache.cs index 43a2b84302..2b4e751b08 100644 --- a/src/Tgstation.Server.Host/Security/IdentityCache.cs +++ b/src/Tgstation.Server.Host/Security/IdentityCache.cs @@ -61,8 +61,9 @@ namespace Tgstation.Server.Host.Security if (cachedIdentities.TryGetValue(user.Id, out var identCache)) { logger.LogTrace("Expiring previously cached identity..."); - identCache.Dispose(); //also clears it out + identCache.Dispose(); // also clears it out } + identCache = new IdentityCacheObject(systemIdentity.Clone(), asyncDelayer, () => { logger.LogDebug("Expiring system identity cache for user {1}", uid, user.Id); diff --git a/src/Tgstation.Server.Host/Security/TokenFactory.cs b/src/Tgstation.Server.Host/Security/TokenFactory.cs index 661f52f91a..2974f6d3b1 100644 --- a/src/Tgstation.Server.Host/Security/TokenFactory.cs +++ b/src/Tgstation.Server.Host/Security/TokenFactory.cs @@ -73,13 +73,13 @@ namespace Tgstation.Server.Host.Security throw new ArgumentNullException(nameof(user)); var now = DateTimeOffset.Now; - var nowUnix = now.ToUnixTimeSeconds(); - //this prevents validation conflicts down the line - //tldr we can (theoretically) send a token the same second we receive it - //since unix time rounds down, it looks like it came from before the user changed their password - //this happens occasionally in unit tests - //just delay a second so we can force a round up + + // this prevents validation conflicts down the line + // tldr we can (theoretically) send a token the same second we receive it + // since unix time rounds down, it looks like it came from before the user changed their password + // this happens occasionally in unit tests + // just delay a second so we can force a round up var lpuUnix = user.LastPasswordUpdate?.ToUnixTimeSeconds(); if (nowUnix == lpuUnix) await asyncDelayer.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs b/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs index 43065b1b5b..5125613971 100644 --- a/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs +++ b/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs @@ -63,13 +63,13 @@ namespace Tgstation.Server.Host.Security { if (identity != null) { - //var newIdentity = (WindowsIdentity)identity.Clone(); //doesn't work because of https://github.com/dotnet/corefx/issues/31841 - - var newIdentity = new WindowsIdentity(identity.Token); //the handle is cloned internally + // var newIdentity = (WindowsIdentity)identity.Clone(); //doesn't work because of https://github.com/dotnet/corefx/issues/31841 + var newIdentity = new WindowsIdentity(identity.Token); // the handle is cloned internally return new WindowsSystemIdentity(newIdentity); } - //can't clone a UP, shouldn't be trying to anyway, cloning is for impersonation + + // can't clone a UP, shouldn't be trying to anyway, cloning is for impersonation throw new InvalidOperationException("Cannot clone a UserPrincipal based WindowsSystemIdentity!"); } diff --git a/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs index e1deb301f0..ff4b49c420 100644 --- a/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs +++ b/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs @@ -77,13 +77,15 @@ namespace Tgstation.Server.Host.Security cancellationToken.ThrowIfCancellationRequested(); } } + return principal != null; - }; - + } + if (!TryGetPrincipalFromContextType(ContextType.Machine) && !TryGetPrincipalFromContextType(ContextType.Domain)) return null; return (ISystemIdentity)new WindowsSystemIdentity(principal); - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + }, + cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); /// public Task CreateSystemIdentity(string username, string password, CancellationToken cancellationToken) => Task.Factory.StartNew(() => @@ -105,8 +107,8 @@ namespace Tgstation.Server.Host.Security logger.LogTrace("Successfully logged in username {0}!", originalUsername); - using (var handle = new SafeAccessTokenHandle(token)) //checked internally, windows identity always duplicates the handle when constructed with a userToken - return (ISystemIdentity)new WindowsSystemIdentity(new WindowsIdentity(handle.DangerousGetHandle())); //https://github.com/dotnet/corefx/blob/6ed61acebe3214fcf79b4274f2bb9b55c0604a4d/src/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs#L271 + using (var handle = new SafeAccessTokenHandle(token)) // checked internally, windows identity always duplicates the handle when constructed with a userToken + return (ISystemIdentity)new WindowsSystemIdentity(new WindowsIdentity(handle.DangerousGetHandle())); // https://github.com/dotnet/corefx/blob/6ed61acebe3214fcf79b4274f2bb9b55c0604a4d/src/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs#L271 }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); } } diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index 1786fd6a19..3902197ca2 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -48,6 +48,11 @@ namespace Tgstation.Server.Host /// CancellationTokenSource cancellationTokenSource; + /// + /// The to propagate when the server terminates + /// + Exception propagatedException; + /// /// If a server update has been or is being applied /// @@ -63,6 +68,8 @@ namespace Tgstation.Server.Host this.webHostBuilder = webHostBuilder ?? throw new ArgumentNullException(nameof(webHostBuilder)); this.updatePath = updatePath; + webHostBuilder.ConfigureServices(serviceCollection => serviceCollection.AddSingleton(this)); + restartHandlers = new List(); } @@ -72,13 +79,22 @@ namespace Tgstation.Server.Host /// If should be checked void CheckSanity(bool checkWatchdog) { - if (checkWatchdog && !WatchdogPresent) + if (checkWatchdog && !WatchdogPresent && propagatedException == null) throw new InvalidOperationException("Server restarts are not supported"); if (cancellationTokenSource == null || logger == null) throw new InvalidOperationException("Tried to control a non-running Server!"); } + /// + /// Re-throw if it exists + /// + void CheckExceptionPropagation() + { + if (propagatedException != null) + throw propagatedException; + } + /// public async Task RunAsync(CancellationToken cancellationToken) { @@ -94,16 +110,21 @@ namespace Tgstation.Server.Host }; fsWatcher.EnableRaisingEvents = true; } - using (var webHost = webHostBuilder - .UseStartup() - .ConfigureServices(serviceCollection => serviceCollection.AddSingleton(this)) - .Build() - ) - { - logger = webHost.Services.GetRequiredService>(); - await webHost.RunAsync(cancellationTokenSource.Token).ConfigureAwait(false); - } + + using (var webHost = webHostBuilder.Build()) + try + { + logger = webHost.Services.GetRequiredService>(); + await webHost.RunAsync(cancellationTokenSource.Token).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + CheckExceptionPropagation(); + throw; + } } + + CheckExceptionPropagation(); } /// @@ -127,6 +148,7 @@ namespace Tgstation.Server.Host logger.LogTrace("Aborted due to concurrency conflict!"); return false; } + updating = true; } @@ -153,17 +175,18 @@ namespace Tgstation.Server.Host updating = false; try { - //important to not leave this directory around if possible + // important to not leave this directory around if possible await ioManager.DeleteDirectory(updatePath, default).ConfigureAwait(false); } catch (Exception e2) { throw new AggregateException(e, e2); } + throw; } - await Restart(version).ConfigureAwait(false); + await Restart(version, null).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -203,18 +226,20 @@ namespace Tgstation.Server.Host restartHandlers.Remove(handler); }); } + return new RestartRegistration(() => { }); } /// - public Task Restart() => Restart(null); + public Task Restart() => Restart(null, null); /// /// Implements /// /// The of any potential updates being applied - /// - async Task Restart(Version newVersion) + /// The potential value of + /// A representing the running operation + async Task Restart(Version newVersion, Exception exception) { CheckSanity(true); @@ -227,34 +252,39 @@ namespace Tgstation.Server.Host logger.LogTrace("Aborted due to concurrency conflict!"); return; } + RestartRequested = true; + propagatedException = exception; } - logger.LogInformation("Restarting server..."); + if (exception == null) + using (var cts = new CancellationTokenSource()) + { + logger.LogInformation("Restarting server..."); + var cancellationToken = cts.Token; + var eventsTask = Task.WhenAll(restartHandlers.Select(x => x.HandleRestart(newVersion, cancellationToken)).ToList()); - using (var cts = new CancellationTokenSource()) - { - logger.LogTrace("Running restart handlers..."); - var cancellationToken = cts.Token; - var eventsTask = Task.WhenAll(restartHandlers.Select(x => x.HandleRestart(newVersion, cancellationToken)).ToList()); - //YA GOT 10 SECONDS - var expiryTask = Task.Delay(TimeSpan.FromSeconds(10)); - await Task.WhenAny(eventsTask, expiryTask).ConfigureAwait(false); - logger.LogTrace("Joining restart handlers..."); - cts.Cancel(); - try - { - await eventsTask.ConfigureAwait(false); + // YA GOT 10 SECONDS + var expiryTask = Task.Delay(TimeSpan.FromSeconds(10)); + await Task.WhenAny(eventsTask, expiryTask).ConfigureAwait(false); + logger.LogTrace("Joining restart handlers..."); + cts.Cancel(); + try + { + await eventsTask.ConfigureAwait(false); + } + catch (OperationCanceledException) { } + catch (Exception e) + { + logger.LogError("Restart handlers error! Exception: {0}", e); + } } - catch (OperationCanceledException) { } - catch (Exception e) - { - logger.LogError("Restart handlers error! Exception: {0}", e); - } - } logger.LogTrace("Stopping host..."); cancellationTokenSource.Cancel(); } + + /// + public Task Die(Exception exception) => Restart(null, exception); } } diff --git a/src/Tgstation.Server.Host/ServerFactory.cs b/src/Tgstation.Server.Host/ServerFactory.cs index cefdfd8897..d06f20fa93 100644 --- a/src/Tgstation.Server.Host/ServerFactory.cs +++ b/src/Tgstation.Server.Host/ServerFactory.cs @@ -1,5 +1,10 @@ using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; using System; +using System.IO; +using System.Reflection; +using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host { @@ -7,6 +12,18 @@ namespace Tgstation.Server.Host public sealed class ServerFactory : IServerFactory { /// - public IServer CreateServer(string[] args, string updatePath) => new Server(WebHost.CreateDefaultBuilder(args ?? throw new ArgumentNullException(nameof(args))), updatePath); + public IServer CreateServer(string[] args, string updatePath) + { + var webHost = WebHost.CreateDefaultBuilder(args ?? throw new ArgumentNullException(nameof(args))) + .ConfigureAppConfiguration((context, configurationBuilder) => configurationBuilder.SetBasePath(Directory.GetCurrentDirectory())) + .UseStartup() + .SuppressStatusMessages(true) + .UseShutdownTimeout(TimeSpan.FromMinutes(1)); + + if(updatePath != null) + webHost.UseContentRoot(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)); + + return new Server(webHost, updatePath); + } } } diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index edb20c1711..199655af18 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -3,21 +3,45 @@ netcoreapp2.1 Full - 4.0.1.3 + 4.0.1.4 + latest + ../../build/analyzers.ruleset - latest true bin\Release\netcoreapp2.1\Tgstation.Server.Host.xml - 1701;1702;1705;CA2227 - latest - 1701;1702;1705;CA2227 + 1701;1702;SA1652 + bin\Debug\netcoreapp2.1\Tgstation.Server.Host.xml + + + ClientApp/node_modules + + ClientApp/node_modules/.install-stamp + + + + + + + + + + + + + + + + + + + @@ -30,6 +54,7 @@ + all runtime; build; native; contentfiles; analyzers @@ -47,9 +72,14 @@ + + all + runtime; build; native; contentfiles; analyzers + + @@ -68,6 +98,15 @@ + + + + + + + + + @@ -76,4 +115,7 @@ + + + diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index 2da5abc3b2..866c3c8eb1 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -33,6 +33,11 @@ } } }, + "ControlPanel": { + "Enable": false, + "AllowAnyOrigin": false, + "AllowedOrigins": [] + }, "Updates": { "GitHubRepositoryId": 92952846, "GitTagPrefix": "tgstation-server-v", diff --git a/tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj b/tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj index 1ec7f54612..fd32c0dc52 100644 --- a/tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj +++ b/tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj @@ -7,6 +7,14 @@ latest + + + + + + + + diff --git a/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj b/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj index 4041253f61..29891d93e0 100644 --- a/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj +++ b/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj @@ -7,6 +7,14 @@ latest + + + + + + + + diff --git a/tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj b/tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj index 7e17c460e2..f8921ac7ac 100644 --- a/tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj +++ b/tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj @@ -7,6 +7,14 @@ latest + + + + + + + + diff --git a/tests/Tgstation.Server.Host.Service.Tests/TestServerService.cs b/tests/Tgstation.Server.Host.Service.Tests/TestServerService.cs index 4cbfc16d8e..d4e4545111 100644 --- a/tests/Tgstation.Server.Host.Service.Tests/TestServerService.cs +++ b/tests/Tgstation.Server.Host.Service.Tests/TestServerService.cs @@ -34,23 +34,30 @@ namespace Tgstation.Server.Host.Service.Tests var mockWatchdog = new Mock(); var args = Array.Empty(); - CancellationToken cancellationToken; - mockWatchdog.Setup(x => x.RunAsync(false, args, It.IsAny())).Callback((bool x, string[] _, CancellationToken token) => cancellationToken = token).Returns(Task.CompletedTask).Verifiable(); + CancellationToken cancellationToken, originalToken; + using(var cts = new CancellationTokenSource()) + { + originalToken = cts.Token; + cancellationToken = originalToken; + } + var cancelled = false; + + mockWatchdog.Setup(x => x.RunAsync(false, args, It.IsAny())).Callback((bool x, string[] _, CancellationToken token) => token.Register(() => cancelled = true)).Returns(Task.CompletedTask).Verifiable(); var mockWatchdogFactory = new Mock(); var mockLoggerFactory = new LoggerFactory(); mockWatchdogFactory.Setup(x => x.CreateWatchdog(mockLoggerFactory)).Returns(mockWatchdog.Object).Verifiable(); using (var service = new ServerService(mockWatchdogFactory.Object, mockLoggerFactory, default)) { + Assert.IsFalse(cancelled); onStart.Invoke(service, new object[] { args }); + mockWatchdog.VerifyAll(); - Assert.IsFalse(cancellationToken.IsCancellationRequested); + Assert.AreNotSame(cancellationToken, originalToken); onStop.Invoke(service, Array.Empty()); - Assert.IsTrue(cancellationToken.IsCancellationRequested); - mockWatchdog.VerifyAll(); + Assert.IsTrue(cancelled); } - mockWatchdogFactory.VerifyAll(); } } diff --git a/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj b/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj index b2b93b20f3..d2375010bb 100644 --- a/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj +++ b/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj @@ -28,6 +28,7 @@ prompt 4 7.1 + pdbonly @@ -37,6 +38,7 @@ prompt 4 7.1 + diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs index 191b5d05b4..94eba126e5 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs +++ b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs @@ -32,16 +32,21 @@ namespace Tgstation.Server.Host.Core.Tests var app = new Application(mockConfiguration.Object, mockHostingEnvironment.Object); Assert.ThrowsException(() => app.ConfigureServices(null)); - Assert.ThrowsException(() => app.Configure(null, null, null, null)); + Assert.ThrowsException(() => app.Configure(null, null, null, null, null)); var mockAppBuilder = new Mock(); - Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, null, null, null)); + Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, null, null, null, null)); var mockServerControl = new Mock(); - Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, null, null)); + Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, null, null, null)); var mockTokenFactory = new Mock(); - Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, null)); + Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, null, null)); + + var mockControlPanelOptions = new Mock>(); + mockControlPanelOptions.SetupGet(x => x.Value).Returns(new ControlPanelConfiguration()).Verifiable(); + Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockControlPanelOptions.Object, null)); + mockControlPanelOptions.VerifyAll(); } class MockSetupWizard : ISetupWizard diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestServiceCollectionExtensions.cs b/tests/Tgstation.Server.Host.Tests/Core/TestServiceCollectionExtensions.cs new file mode 100644 index 0000000000..3cb14f8dd8 --- /dev/null +++ b/tests/Tgstation.Server.Host.Tests/Core/TestServiceCollectionExtensions.cs @@ -0,0 +1,47 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System; + +namespace Tgstation.Server.Host.Core.Tests +{ + [TestClass] + public sealed class TestServiceCollectionExtensions + { + class GoodConfig + { + public const string Section = "asdf"; + } + + class BadConfig1 + { + const string Section = "asdf"; + } + + class BadConfig2 + { + public const bool Section = false; + } + + class BadConfig3 + { + //nah + } + + [TestMethod] + public void TestUseStandardConfig() + { + var serviceCollection = new ServiceCollection(); + var mockConfig = new Mock(); + mockConfig.Setup(x => x.GetSection(It.IsNotNull())).Returns(mockConfig.Object).Verifiable(); + Assert.ThrowsException(() => ServiceCollectionExtensions.UseStandardConfig(null, null)); + Assert.ThrowsException(() => serviceCollection.UseStandardConfig(null)); + serviceCollection.UseStandardConfig(mockConfig.Object); + Assert.ThrowsException(() => serviceCollection.UseStandardConfig(mockConfig.Object)); + Assert.ThrowsException(() => serviceCollection.UseStandardConfig(mockConfig.Object)); + Assert.ThrowsException(() => serviceCollection.UseStandardConfig(mockConfig.Object)); + mockConfig.Verify(); + } + } +} diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestSetupWizard.cs b/tests/Tgstation.Server.Host.Tests/Core/TestSetupWizard.cs index 7824a93c27..8dbe49f363 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestSetupWizard.cs +++ b/tests/Tgstation.Server.Host.Tests/Core/TestSetupWizard.cs @@ -144,6 +144,9 @@ namespace Tgstation.Server.Host.Core.Tests "fake token", //logging config "no", + //cp config + "y", + "y", //saved, now for second run //this time use defaults amap String.Empty, @@ -156,6 +159,7 @@ namespace Tgstation.Server.Host.Core.Tests "user", "pass", //general config + "y", String.Empty, String.Empty, String.Empty, @@ -164,6 +168,10 @@ namespace Tgstation.Server.Host.Core.Tests "not actually verified because lol mocks /../!@#$%^&*()/..///.", "Warning", String.Empty, + //cp config + "y", + "n", + String.Empty, //third run, we already hit all the code coverage so just get through it String.Empty, nameof(DatabaseType.MariaDB), @@ -172,6 +180,8 @@ namespace Tgstation.Server.Host.Core.Tests "y", "user", "pass", + //general + "y", String.Empty, String.Empty, String.Empty, @@ -181,7 +191,11 @@ namespace Tgstation.Server.Host.Core.Tests String.Empty, "fake", "None", - "Critical" + "Critical", + //cp config + "y", + "n", + "http://fake.com, https://example.org", }; var inputPos = 0; diff --git a/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj b/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj index c075c20e1c..454e3d69e9 100644 --- a/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj +++ b/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj @@ -7,6 +7,14 @@ latest + + + + + + + + diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj b/tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj index 5f4e69a72a..27bb53e866 100644 --- a/tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj +++ b/tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj @@ -11,10 +11,12 @@ false + latest + diff --git a/tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj b/tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj index 6927548dae..84ddd0386a 100644 --- a/tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj +++ b/tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj @@ -8,10 +8,12 @@ 7.1 + 7.1 + diff --git a/tgstation-server.sln b/tgstation-server.sln index a7fb21ff65..7ca6d9e0f9 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -20,9 +20,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Conso EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{6FF654E6-3E2C-46D4-872D-D528F77D6973}" ProjectSection(SolutionItems) = preProject + build\analyzers.ruleset = build\analyzers.ruleset build\BuildDox.ps1 = build\BuildDox.ps1 build\Dockerfile = build\Dockerfile build\install_byond.sh = build\install_byond.sh + build\prep_deployment.ps1 = build\prep_deployment.ps1 + build\stylecop.json = build\stylecop.json build\test_core.sh = build\test_core.sh build\tgs.docker.sh = build\tgs.docker.sh build\tgs.ico = build\tgs.ico @@ -64,6 +67,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "core", "core", "{DCCBA9DA-4 src\DMAPI\tgs\core\_definitions.dm = src\DMAPI\tgs\core\_definitions.dm src\DMAPI\tgs\core\core.dm = src\DMAPI\tgs\core\core.dm src\DMAPI\tgs\core\datum.dm = src\DMAPI\tgs\core\datum.dm + src\DMAPI\tgs\core\tgs_version.dm = src\DMAPI\tgs\core\tgs_version.dm EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "v3210", "v3210", "{1B228ACB-60C3-4DF9-B716-9F0BD31F6766}" @@ -114,6 +118,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{E821 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Tests", "tests\Tgstation.Server.Tests\Tgstation.Server.Tests.csproj", "{09056964-1C74-445A-96EC-33F6DFC07916}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReleaseNotes", "tools\ReleaseNotes\ReleaseNotes.csproj", "{5CB51532-55F0-4255-B6E5-69ED5CCD14CD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -172,6 +178,10 @@ Global {09056964-1C74-445A-96EC-33F6DFC07916}.Debug|Any CPU.Build.0 = Debug|Any CPU {09056964-1C74-445A-96EC-33F6DFC07916}.Release|Any CPU.ActiveCfg = Release|Any CPU {09056964-1C74-445A-96EC-33F6DFC07916}.Release|Any CPU.Build.0 = Release|Any CPU + {5CB51532-55F0-4255-B6E5-69ED5CCD14CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5CB51532-55F0-4255-B6E5-69ED5CCD14CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5CB51532-55F0-4255-B6E5-69ED5CCD14CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5CB51532-55F0-4255-B6E5-69ED5CCD14CD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -183,6 +193,7 @@ Global {3BB10856-AA01-43D1-AAB0-A013085426D6} = {7E4E7CF6-A48D-410E-8191-C49C6B5AB9CC} {82066812-6C73-4360-943B-B23F2F491261} = {7E4E7CF6-A48D-410E-8191-C49C6B5AB9CC} {057FAC33-CC31-4948-91C6-B0977C335890} = {F7765A4B-021C-454E-ABB1-2AB1B85F91B4} + {5CB51532-55F0-4255-B6E5-69ED5CCD14CD} = {A55C1117-5808-4AB2-BEA6-4D4A3E66A2F2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DFD36C95-3E49-41C7-ACDB-86BAF5B18A79} diff --git a/tools/ReleaseNotes/Program.cs b/tools/ReleaseNotes/Program.cs new file mode 100644 index 0000000000..c62af92615 --- /dev/null +++ b/tools/ReleaseNotes/Program.cs @@ -0,0 +1,309 @@ +using Octokit; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ReleaseNotes +{ + /// + /// Contains the application entrypoint + /// + static class Program + { + /// + /// The entrypoint for the + /// + static async Task Main(string[] args) + { + if (args.Length < 1) + { + Console.WriteLine("Missing version argument!"); + return 1; + } + + var versionString = args[0]; + if (!Version.TryParse(versionString, out var version)) + { + Console.WriteLine("Invalid version: " + versionString); + return 2; + } + + var doNotCloseMilestone = args.Length >= 2 && args[1].ToUpperInvariant() == "--NO-CLOSE"; + + const string ReleaseNotesEnvVar = "TGS_RELEASE_NOTES_TOKEN"; + var githubToken = Environment.GetEnvironmentVariable(ReleaseNotesEnvVar); + if (String.IsNullOrWhiteSpace(githubToken)) + { + Console.WriteLine("Missing " + ReleaseNotesEnvVar + " environment variable!"); + return 3; + } + + try + { + var client = new GitHubClient(new ProductHeaderValue("tgs_release_notes")); + client.Credentials = new Credentials(githubToken); + + const string RepoOwner = "tgstation"; + const string RepoName = "tgstation-server"; + + var releasesTask = client.Repository.Release.GetAll(RepoOwner, RepoName); + + Console.WriteLine("Getting pull requests in milestone " + versionString + "..."); + var milestonePRs = await client.Search.SearchIssues(new SearchIssuesRequest + { + Milestone = versionString, + Type = IssueTypeQualifier.PullRequest, + Repos = { { RepoOwner, RepoName } } + }).ConfigureAwait(false); + + if (milestonePRs.IncompleteResults) + { + Console.WriteLine("Incomplete results for milestone PRs query!"); + return 5; + } + Console.WriteLine(milestonePRs.Items.Count + " total pull requests"); + + Task milestoneTask = null; + var milestoneTaskLock = new object(); + var releaseDictionary = new Dictionary>(); + var authorizedUsers = new Dictionary>(); + + async Task GetReleaseNotesFromPR(Issue pullRequest) + { + //need to check it was merged + var fullPR = await client.Repository.PullRequest.Get(RepoOwner, RepoName, pullRequest.Number).ConfigureAwait(false); + + async Task GetMilestone() + { + if (fullPR.Milestone == null) + return null; + return await client.Issue.Milestone.Get(RepoOwner, RepoName, fullPR.Milestone.Number); + }; + + lock (milestoneTaskLock) + if (milestoneTask == null) + milestoneTask = GetMilestone(); + + if (!fullPR.Merged) + return; + + async Task BuildNotesFromComment(string comment, User user) + { + var commentSplits = comment.Split('\n'); + var notesOpen = false; + var notesClosed = false; + var notes = new List(); + foreach (var line in commentSplits) + { + var trimmedLine = line.Trim(); + if (!notesOpen) + { + notesOpen = trimmedLine.StartsWith(":cl:", StringComparison.Ordinal); + notesClosed = false; + continue; + } + if (trimmedLine.StartsWith("/:cl:", StringComparison.Ordinal)) + { + notesClosed = true; + notesOpen = false; + continue; + } + if (trimmedLine.Length == 0) + continue; + notes.Add(trimmedLine); + } + if (!notesClosed || notes.Count == 0) + return; + + Task authTask; + TaskCompletionSource ourTcs = null; + lock (authorizedUsers) + { + if (!authorizedUsers.TryGetValue(user.Id, out authTask)) + { + ourTcs = new TaskCompletionSource(); + authTask = ourTcs.Task; + authorizedUsers.Add(user.Id, authTask); + } + } + + if (ourTcs != null) + try + { + //check if the user has access + var perm = await client.Repository.Collaborator.ReviewPermission(RepoOwner, RepoName, user.Login).ConfigureAwait(false); + ourTcs.SetResult(perm.Permission == PermissionLevel.Write || perm.Permission == PermissionLevel.Admin); + } + catch + { + ourTcs.SetResult(false); + throw; + } + + var authorized = await authTask.ConfigureAwait(false); + if (!authorized) + return; + + lock (releaseDictionary) + { + foreach (var I in notes) + Console.WriteLine("#" + fullPR.Number + " - " + I + " (@" + user.Login + ")"); + if (releaseDictionary.TryGetValue(fullPR.Number, out var currentValues)) + currentValues.AddRange(notes); + else + releaseDictionary.Add(fullPR.Number, notes); + } + } + + var comments = await client.Issue.Comment.GetAllForIssue(RepoOwner, RepoName, fullPR.Number).ConfigureAwait(false); + await Task.WhenAll(BuildNotesFromComment(fullPR.Body, fullPR.User), Task.WhenAll(comments.Select(x => BuildNotesFromComment(x.Body, x.User)))).ConfigureAwait(false); + } + + var tasks = new List(); + foreach (var I in milestonePRs.Items) + tasks.Add(GetReleaseNotesFromPR(I)); + + var releases = await releasesTask.ConfigureAwait(false); + + var releasingSuite = version.Major; + + Version highestReleaseVersion = null; + Release highestRelease = null; + foreach (var I in releases) + { + if (!Version.TryParse(I.TagName.Replace("tgstation-server-v", String.Empty), out var currentReleaseVersion)) + { + Console.WriteLine("WARNING: Unable to determine version of release " + I.HtmlUrl); + continue; + } + + if (currentReleaseVersion.Major == releasingSuite && (highestReleaseVersion == null || currentReleaseVersion > highestReleaseVersion)) + { + highestReleaseVersion = currentReleaseVersion; + highestRelease = I; + } + } + + if (highestReleaseVersion == null) + { + Console.WriteLine("Unable to determine highest release version for suite " + releasingSuite + "!"); + return 6; + } + + var oldNotes = highestRelease.Body; + + var splits = new List(oldNotes.Split('\n')); + //trim away all the lines that don't start with # + + string keepThisRelease; + if (version.Revision == 0) + if (version.Build == 0) + keepThisRelease = "# "; + else + keepThisRelease = "## "; + else + keepThisRelease = "### "; + + for (; !splits[0].StartsWith(keepThisRelease, StringComparison.Ordinal); splits.RemoveAt(0)) + if (splits.Count == 1) + { + Console.WriteLine("Error formatting release notes: Can't detemine notes start!"); + return 7; + } + + oldNotes = String.Join('\n', splits); + + string prefix; + switch (releasingSuite) + { + case 3: + prefix = "The /tg/station server suite"; + break; + default: + prefix = "See https://tgstation.github.io/tgstation-server for installation instructions"; + break; + } + + var newNotes = new StringBuilder(prefix); + newNotes.Append(Environment.NewLine); + newNotes.Append(Environment.NewLine); + if (version.Revision == 0) + if (version.Build == 0) + { + newNotes.Append("# [Version "); + newNotes.Append(version.Minor); + } + else + { + newNotes.Append("## [Changelog for "); + newNotes.Append(version.Build); + newNotes.Append(".x"); + } + else + { + newNotes.Append("### [Patch "); + newNotes.Append(version.Revision); + } + newNotes.Append("]("); + var milestone = await milestoneTask.ConfigureAwait(false); + if (milestone == null) + { + Console.WriteLine("Unable to detemine milestone!"); + return 9; + } + newNotes.Append(milestone.HtmlUrl); + newNotes.Append("?closed=1)"); + newNotes.Append(Environment.NewLine); + + await Task.WhenAll(tasks).ConfigureAwait(false); + + if (releaseDictionary.Count == 0) + { + Console.WriteLine("No release notes for this milestone!"); + return 8; + } + + foreach (var I in releaseDictionary) + foreach (var note in I.Value) + { + newNotes.Append(Environment.NewLine); + newNotes.Append("- "); + newNotes.Append(note); + newNotes.Append(" (#"); + newNotes.Append(I.Key); + newNotes.Append(')'); + } + + newNotes.Append(Environment.NewLine); + newNotes.Append(Environment.NewLine); + newNotes.Append(oldNotes); + + Console.WriteLine("Writing out new release notes..."); + var releaseNotes = newNotes.ToString(); + await File.WriteAllTextAsync("release_notes.md", releaseNotes).ConfigureAwait(false); + + if (doNotCloseMilestone) + Console.WriteLine("Not closing milestone due to parameter!"); + else + { + Console.WriteLine("Closing milestone..."); + await client.Issue.Milestone.Update(RepoOwner, RepoName, milestone.Number, new MilestoneUpdate + { + State = ItemState.Closed + }).ConfigureAwait(false); + } + + return 0; + } + catch (Exception e) + { + Console.WriteLine(e); + return 4; + } + } + } +} diff --git a/tools/ReleaseNotes/README.md b/tools/ReleaseNotes/README.md new file mode 100644 index 0000000000..63aa900ef3 --- /dev/null +++ b/tools/ReleaseNotes/README.md @@ -0,0 +1,7 @@ +This is a small tool to generate TGS release notes from PR descriptions + +Requires environment variable `TGS_RELEASE_NOTES_TOKEN` + +Run it with `dotnet run [--no-close (optionally doesn't close the milestone, USE WHILE DEBUGGING)]` + +Will close the release milestone and output `release_notes.md` with the updated release notes diff --git a/tools/ReleaseNotes/ReleaseNotes.csproj b/tools/ReleaseNotes/ReleaseNotes.csproj new file mode 100644 index 0000000000..64c79d0620 --- /dev/null +++ b/tools/ReleaseNotes/ReleaseNotes.csproj @@ -0,0 +1,13 @@ + + + + Exe + netcoreapp2.1 + latest + + + + + + +