Compare commits

...

36 Commits

Author SHA1 Message Date
Nate McMaster
f35c814fc7 Update dependencies.props to 2.1.0-preview-28193, build tools to 2.1.0-preview1-1010 [ci skip]
Scripted changes:
- updated travis and appveyor.yml files to only build dev, ci, and release branches
- updated dependencies.props
- updated korebuild-lock.txt
- updated korebuild.json to release/2.1 channel
2018-01-31 15:01:11 -08:00
Pranav K
116f33c66c Branching for 2.1.0-preview1 2018-01-23 15:31:36 -08:00
Nate McMaster
c3964a0437 Merge branch 'release/2.0.0' into dev 2018-01-09 13:59:19 -08:00
ASP.NET CI
f291f87dfc Update dependencies.props
[auto-updated: dependencies]
2018-01-06 14:57:16 -08:00
ASP.NET CI
a1c2c18326 Update dependencies.props
[auto-updated: dependencies]
2018-01-04 01:24:07 +00:00
Steve Sanderson
d6588c31bf In Angular CLI middleware, remove additional level of timeouts since it's now covered upstream. Part of #1447 2018-01-03 11:47:17 +00:00
Steve Sanderson
15d2f5a898 Allow explicit configuration of StaticFileOptions in new SPA APIs. Fixes #1424. 2018-01-02 15:44:59 +00:00
Steve Sanderson
814441c933 Allow configuration of SPA startup timeout. Part of #1447 2018-01-02 14:15:33 +00:00
Steve Sanderson
a98c1459b5 When a SPA dev server (or prerendering build) takes too long to start up, only fail current request, not future requests. Fixes #1447 2018-01-02 14:15:20 +00:00
ASP.NET CI
975d537a0a Update dependencies.props
[auto-updated: dependencies]
2017-12-31 21:17:41 +00:00
ASP.NET CI
4af2e8670e Update dependencies.props
[auto-updated: dependencies]
2017-12-18 17:15:05 -08:00
ASP.NET CI
160c91f1b9 Update dependencies.props
[auto-updated: dependencies]
2017-12-13 21:00:41 +00:00
Steve Sanderson
8ded472fe9 Add status code support to SpaPrerenderingExtensions 2017-12-12 12:28:31 +00:00
ASP.NET CI
f9c62ebb5a Update dependencies.props
[auto-updated: dependencies]
2017-12-10 13:01:04 -08:00
Nate McMaster
d5a664e481 Bump version to 2.0.2 2017-12-06 16:20:14 -08:00
John Goldsmith
74512dc3b2 Change HMR install to devDependencies
Changing hot module replacement install from dependencies to devDependencies.  Raised in this Issue: https://github.com/aspnet/JavaScriptServices/issues/1409
2017-12-05 10:42:15 +00:00
Ryan Brandenburg
e6285f30ae Update bootstrappers 2017-12-01 12:29:52 -08:00
Pranav K
bd5793e284 Specify runtime versions to install 2017-11-29 14:09:27 -08:00
Meir017
02bbcb68f1 fixed docs on SocketNodeInstance
namespace was incorrect
2017-11-24 09:53:35 +00:00
Steve Sanderson
18140929e7 Make AngularCliBuilder provide better information about timeouts 2017-11-22 15:14:58 +00:00
Pranav K
50ba6114ee Replace aspnetcore-ci-dev feed with aspnetcore-dev 2017-11-21 15:48:06 -08:00
Nate McMaster
cd3e3c667c Use MSBuild to set NuGet feeds instead of NuGet.config 2017-11-20 12:43:30 -08:00
Pranav K
0de9f0e3ce Use MicrosoftNETCoreApp21PackageVersion to determine the runtime framework in netcoreapp2.1 2017-11-17 13:00:25 -08:00
Steve Sanderson
9b1509a52b Handle @angular/cli not accept requests immediately on startup 2017-11-17 14:56:47 +00:00
Pranav K
a8809f9a96 Update samples and tests to target netcoreapp2.1 2017-11-16 16:59:15 -08:00
Nate McMaster
64389a9bbe Update build tools to 2.0.2-rc1-15526 and dependencies to 2.0.1-rtm-105 2017-10-13 13:11:55 -07:00
Nate McMaster
86e94d7812 Update how PackageReference versions are set
Changes:
 - Remove floating versions
 - Disable myget feeds during a Universe build
 - Use package-specific MSBuild variables. Pattern = `packageId.Pascalize() + "PackageVersion"`, with a few exceptions.
2017-10-09 11:10:58 -07:00
Nate McMaster
9f05a3d34b Use MSBuild to set NuGet feeds instead of NuGet.config 2017-10-02 14:12:55 -07:00
Nate McMaster
63e0af2ee8 Import dependencies.props last to ensure TargetFramework is set first 2017-09-29 17:02:10 -07:00
Nate McMaster
dc5e980efa Update build scripts, tools, and dependencies for 2.0.x 2017-09-20 17:23:18 -07:00
Nate McMaster
e0ab3ddcca Update the list of packages patching in 2.0.x 2017-09-20 13:40:18 -07:00
Ryan Brandenburg
0c058894c2 Update bootstrappers 2017-09-19 14:44:54 -07:00
Ryan Brandenburg
98385cbcb0 Update bootstrappers 2017-09-19 14:44:54 -07:00
Nate McMaster
77cac3b6be Update package feeds and dependencies for 2.0.1 (#1284) 2017-09-18 12:42:26 -07:00
Nate McMaster
051150475f Bump version to 2.0.1 2017-09-15 18:00:22 -07:00
Pranav K
128683be0e Pinning versions for 2.0.0 2017-08-17 15:00:10 -07:00
27 changed files with 286 additions and 141 deletions

View File

@@ -1,21 +1,19 @@
init: init:
- git config --global core.autocrlf true - git config --global core.autocrlf true
install: install:
- ps: Install-Product node 6.9.2 x64 - ps: Install-Product node 6.9.2 x64
branches: branches:
only: only:
- master - dev
- release - /^release\/.*$/
- dev - /^(.*\/)?ci-.*$/
- /^(.*\/)?ci-.*$/
- /^rel\/.*/
build_script: build_script:
- ps: .\run.ps1 default-build - ps: .\run.ps1 default-build
clone_depth: 1 clone_depth: 1
environment: environment:
global: global:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1
test: off test: 'off'
deploy: off deploy: 'off'
os: Visual Studio 2017 os: Visual Studio 2017

View File

@@ -12,8 +12,13 @@ addons:
- zlib1g - zlib1g
mono: none mono: none
os: os:
- linux - linux
- osx - osx
osx_image: xcode7.1 osx_image: xcode7.1
script: script:
- ./build.sh - ./build.sh
branches:
only:
- dev
- /^release\/.*$/
- /^(.*\/)?ci-.*$/

View File

@@ -1,6 +1,7 @@
<Project> <Project>
<Import Project="version.props" /> <Import Project="version.props" />
<Import Project="build\dependencies.props" /> <Import Project="build\dependencies.props" />
<Import Project="build\sources.props" />
<PropertyGroup> <PropertyGroup>
<Product>Microsoft ASP.NET Core</Product> <Product>Microsoft ASP.NET Core</Product>

View File

@@ -1,5 +1,6 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.0' ">$(MicrosoftNETCoreApp20PackageVersion)</RuntimeFrameworkVersion> <RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.0' ">$(MicrosoftNETCoreApp20PackageVersion)</RuntimeFrameworkVersion>
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">$(MicrosoftNETCoreApp21PackageVersion)</RuntimeFrameworkVersion>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View File

@@ -2,8 +2,6 @@
<configuration> <configuration>
<packageSources> <packageSources>
<clear /> <clear />
<add key="AspNetCore" value="https://dotnet.myget.org/F/aspnetcore-ci-dev/api/v3/index.json" /> <!-- Restore sources should be defined in build/sources.props. -->
<add key="AspNetCoreTools" value="https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json" />
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
</packageSources> </packageSources>
</configuration> </configuration>

View File

@@ -1,26 +1,27 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects> <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Label="Package Versions"> <PropertyGroup Label="Package Versions">
<InternalAspNetCoreSdkPackageVersion>2.1.0-preview1-15549</InternalAspNetCoreSdkPackageVersion> <InternalAspNetCoreSdkPackageVersion>2.1.0-preview1-1010</InternalAspNetCoreSdkPackageVersion>
<MicrosoftAspNetCoreDiagnosticsPackageVersion>2.1.0-preview1-27478</MicrosoftAspNetCoreDiagnosticsPackageVersion> <MicrosoftAspNetCoreDiagnosticsPackageVersion>2.1.0-preview1-28193</MicrosoftAspNetCoreDiagnosticsPackageVersion>
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>2.1.0-preview1-27478</MicrosoftAspNetCoreHostingAbstractionsPackageVersion> <MicrosoftAspNetCoreHostingAbstractionsPackageVersion>2.1.0-preview1-28193</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
<MicrosoftAspNetCoreHostingPackageVersion>2.1.0-preview1-27478</MicrosoftAspNetCoreHostingPackageVersion> <MicrosoftAspNetCoreHostingPackageVersion>2.1.0-preview1-28193</MicrosoftAspNetCoreHostingPackageVersion>
<MicrosoftAspNetCoreMvcPackageVersion>2.1.0-preview1-27478</MicrosoftAspNetCoreMvcPackageVersion> <MicrosoftAspNetCoreMvcPackageVersion>2.1.0-preview1-28193</MicrosoftAspNetCoreMvcPackageVersion>
<MicrosoftAspNetCoreMvcTagHelpersPackageVersion>2.1.0-preview1-27478</MicrosoftAspNetCoreMvcTagHelpersPackageVersion> <MicrosoftAspNetCoreMvcTagHelpersPackageVersion>2.1.0-preview1-28193</MicrosoftAspNetCoreMvcTagHelpersPackageVersion>
<MicrosoftAspNetCoreMvcViewFeaturesPackageVersion>2.1.0-preview1-27478</MicrosoftAspNetCoreMvcViewFeaturesPackageVersion> <MicrosoftAspNetCoreMvcViewFeaturesPackageVersion>2.1.0-preview1-28193</MicrosoftAspNetCoreMvcViewFeaturesPackageVersion>
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.1.0-preview1-27478</MicrosoftAspNetCoreServerIISIntegrationPackageVersion> <MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.1.0-preview1-28193</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.1.0-preview1-27478</MicrosoftAspNetCoreServerKestrelPackageVersion> <MicrosoftAspNetCoreServerKestrelPackageVersion>2.1.0-preview1-28193</MicrosoftAspNetCoreServerKestrelPackageVersion>
<MicrosoftAspNetCoreStaticFilesPackageVersion>2.1.0-preview1-27478</MicrosoftAspNetCoreStaticFilesPackageVersion> <MicrosoftAspNetCoreStaticFilesPackageVersion>2.1.0-preview1-28193</MicrosoftAspNetCoreStaticFilesPackageVersion>
<MicrosoftAspNetCoreWebSocketsPackageVersion>2.1.0-preview1-27478</MicrosoftAspNetCoreWebSocketsPackageVersion> <MicrosoftAspNetCoreWebSocketsPackageVersion>2.1.0-preview1-28193</MicrosoftAspNetCoreWebSocketsPackageVersion>
<MicrosoftExtensionsDependencyInjectionPackageVersion>2.1.0-preview1-27478</MicrosoftExtensionsDependencyInjectionPackageVersion> <MicrosoftExtensionsDependencyInjectionPackageVersion>2.1.0-preview1-28193</MicrosoftExtensionsDependencyInjectionPackageVersion>
<MicrosoftExtensionsFileProvidersPhysicalPackageVersion>2.1.0-preview1-27478</MicrosoftExtensionsFileProvidersPhysicalPackageVersion> <MicrosoftExtensionsFileProvidersPhysicalPackageVersion>2.1.0-preview1-28193</MicrosoftExtensionsFileProvidersPhysicalPackageVersion>
<MicrosoftExtensionsLoggingConsolePackageVersion>2.1.0-preview1-27478</MicrosoftExtensionsLoggingConsolePackageVersion> <MicrosoftExtensionsLoggingConsolePackageVersion>2.1.0-preview1-28193</MicrosoftExtensionsLoggingConsolePackageVersion>
<MicrosoftExtensionsLoggingDebugPackageVersion>2.1.0-preview1-27478</MicrosoftExtensionsLoggingDebugPackageVersion> <MicrosoftExtensionsLoggingDebugPackageVersion>2.1.0-preview1-28193</MicrosoftExtensionsLoggingDebugPackageVersion>
<MicrosoftNETCoreApp20PackageVersion>2.0.0</MicrosoftNETCoreApp20PackageVersion> <MicrosoftNETCoreApp20PackageVersion>2.0.0</MicrosoftNETCoreApp20PackageVersion>
<MicrosoftNETCoreApp21PackageVersion>2.1.0-preview1-26122-01</MicrosoftNETCoreApp21PackageVersion>
<NewtonsoftJsonPackageVersion>10.0.1</NewtonsoftJsonPackageVersion> <NewtonsoftJsonPackageVersion>10.0.1</NewtonsoftJsonPackageVersion>
<SystemThreadingTasksDataflowPackageVersion>4.8.0</SystemThreadingTasksDataflowPackageVersion> <SystemThreadingTasksDataflowPackageVersion>4.9.0-preview1-26119-06</SystemThreadingTasksDataflowPackageVersion>
</PropertyGroup> </PropertyGroup>
<Import Project="$(DotNetPackageVersionPropsPath)" Condition=" '$(DotNetPackageVersionPropsPath)' != '' " /> <Import Project="$(DotNetPackageVersionPropsPath)" Condition=" '$(DotNetPackageVersionPropsPath)' != '' " />
</Project> </Project>

View File

@@ -1,7 +1,14 @@
<Project> <Project>
<Import Project="dependencies.props" />
<PropertyGroup> <PropertyGroup>
<!-- These properties are use by the automation that updates dependencies.props --> <!-- These properties are use by the automation that updates dependencies.props -->
<LineupPackageId>Internal.AspNetCore.Universe.Lineup</LineupPackageId> <LineupPackageId>Internal.AspNetCore.Universe.Lineup</LineupPackageId>
<LineupPackageRestoreSource>https://dotnet.myget.org/F/aspnetcore-ci-dev/api/v3/index.json</LineupPackageRestoreSource> <LineupPackageRestoreSource>https://dotnet.myget.org/F/aspnetcore-release/api/v3/index.json</LineupPackageRestoreSource>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp20PackageVersion)" />
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp21PackageVersion)" />
</ItemGroup>
</Project> </Project>

16
build/sources.props Normal file
View File

@@ -0,0 +1,16 @@
<Project>
<Import Project="$(DotNetRestoreSourcePropsPath)" Condition="'$(DotNetRestoreSourcePropsPath)' != ''"/>
<PropertyGroup Label="RestoreSources">
<RestoreSources>$(DotNetRestoreSources)</RestoreSources>
<RestoreSources Condition="'$(DotNetBuildOffline)' != 'true' AND '$(AspNetUniverseBuildOffline)' != 'true' ">
$(RestoreSources);
https://dotnet.myget.org/F/aspnetcore-release/api/v3/index.json;
https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json;
</RestoreSources>
<RestoreSources Condition="'$(DotNetBuildOffline)' != 'true'">
$(RestoreSources);
https://api.nuget.org/v3/index.json;
</RestoreSources>
</PropertyGroup>
</Project>

View File

@@ -1,2 +1,2 @@
version:2.1.0-preview1-15549 version:2.1.0-preview1-1010
commithash:f570e08585fec510dd60cd4bfe8795388b757a95 commithash:75ca924dfbd673c38841025b04c4dcd93b84f56d

View File

@@ -1,6 +1,6 @@
{ {
"$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/dev/tools/korebuild.schema.json", "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/release/2.1/tools/korebuild.schema.json",
"channel": "dev", "channel": "release/2.1",
"toolsets": { "toolsets": {
"nodejs": { "nodejs": {
"required": true, "required": true,

17
run.ps1
View File

@@ -29,6 +29,9 @@ Updates KoreBuild to the latest version even if a lock file is present.
.PARAMETER ConfigFile .PARAMETER ConfigFile
The path to the configuration file that stores values. Defaults to korebuild.json. The path to the configuration file that stores values. Defaults to korebuild.json.
.PARAMETER ToolsSourceSuffix
The Suffix to append to the end of the ToolsSource. Useful for query strings in blob stores.
.PARAMETER Arguments .PARAMETER Arguments
Arguments to be passed to the command Arguments to be passed to the command
@@ -51,7 +54,7 @@ Example config file:
#> #>
[CmdletBinding(PositionalBinding = $false)] [CmdletBinding(PositionalBinding = $false)]
param( param(
[Parameter(Mandatory=$true, Position = 0)] [Parameter(Mandatory = $true, Position = 0)]
[string]$Command, [string]$Command,
[string]$Path = $PSScriptRoot, [string]$Path = $PSScriptRoot,
[Alias('c')] [Alias('c')]
@@ -63,6 +66,7 @@ param(
[Alias('u')] [Alias('u')]
[switch]$Update, [switch]$Update,
[string]$ConfigFile, [string]$ConfigFile,
[string]$ToolsSourceSuffix,
[Parameter(ValueFromRemainingArguments = $true)] [Parameter(ValueFromRemainingArguments = $true)]
[string[]]$Arguments [string[]]$Arguments
) )
@@ -79,7 +83,7 @@ function Get-KoreBuild {
$lockFile = Join-Path $Path 'korebuild-lock.txt' $lockFile = Join-Path $Path 'korebuild-lock.txt'
if (!(Test-Path $lockFile) -or $Update) { if (!(Test-Path $lockFile) -or $Update) {
Get-RemoteFile "$ToolsSource/korebuild/channels/$Channel/latest.txt" $lockFile Get-RemoteFile "$ToolsSource/korebuild/channels/$Channel/latest.txt" $lockFile $ToolsSourceSuffix
} }
$version = Get-Content $lockFile | Where-Object { $_ -like 'version:*' } | Select-Object -first 1 $version = Get-Content $lockFile | Where-Object { $_ -like 'version:*' } | Select-Object -first 1
@@ -96,7 +100,7 @@ function Get-KoreBuild {
try { try {
$tmpfile = Join-Path ([IO.Path]::GetTempPath()) "KoreBuild-$([guid]::NewGuid()).zip" $tmpfile = Join-Path ([IO.Path]::GetTempPath()) "KoreBuild-$([guid]::NewGuid()).zip"
Get-RemoteFile $remotePath $tmpfile Get-RemoteFile $remotePath $tmpfile $ToolsSourceSuffix
if (Get-Command -Name 'Expand-Archive' -ErrorAction Ignore) { if (Get-Command -Name 'Expand-Archive' -ErrorAction Ignore) {
# Use built-in commands where possible as they are cross-plat compatible # Use built-in commands where possible as they are cross-plat compatible
Expand-Archive -Path $tmpfile -DestinationPath $korebuildPath Expand-Archive -Path $tmpfile -DestinationPath $korebuildPath
@@ -124,7 +128,7 @@ function Join-Paths([string]$path, [string[]]$childPaths) {
return $path return $path
} }
function Get-RemoteFile([string]$RemotePath, [string]$LocalPath) { function Get-RemoteFile([string]$RemotePath, [string]$LocalPath, [string]$RemoteSuffix) {
if ($RemotePath -notlike 'http*') { if ($RemotePath -notlike 'http*') {
Copy-Item $RemotePath $LocalPath Copy-Item $RemotePath $LocalPath
return return
@@ -134,7 +138,7 @@ function Get-RemoteFile([string]$RemotePath, [string]$LocalPath) {
while ($retries -gt 0) { while ($retries -gt 0) {
$retries -= 1 $retries -= 1
try { try {
Invoke-WebRequest -UseBasicParsing -Uri $RemotePath -OutFile $LocalPath Invoke-WebRequest -UseBasicParsing -Uri $($RemotePath + $RemoteSuffix) -OutFile $LocalPath
return return
} }
catch { catch {
@@ -161,7 +165,8 @@ if (Test-Path $ConfigFile) {
if (!($Channel) -and (Get-Member -Name 'channel' -InputObject $config)) { [string] $Channel = $config.channel } if (!($Channel) -and (Get-Member -Name 'channel' -InputObject $config)) { [string] $Channel = $config.channel }
if (!($ToolsSource) -and (Get-Member -Name 'toolsSource' -InputObject $config)) { [string] $ToolsSource = $config.toolsSource} if (!($ToolsSource) -and (Get-Member -Name 'toolsSource' -InputObject $config)) { [string] $ToolsSource = $config.toolsSource}
} }
} catch { }
catch {
Write-Warning "$ConfigFile could not be read. Its settings will be ignored." Write-Warning "$ConfigFile could not be read. Its settings will be ignored."
Write-Warning $Error[0] Write-Warning $Error[0]
} }

30
run.sh
View File

@@ -17,6 +17,7 @@ update=false
repo_path="$DIR" repo_path="$DIR"
channel='' channel=''
tools_source='' tools_source=''
tools_source_suffix=''
# #
# Functions # Functions
@@ -29,13 +30,14 @@ __usage() {
echo " <Arguments>... Arguments passed to the command. Variable number of arguments allowed." echo " <Arguments>... Arguments passed to the command. Variable number of arguments allowed."
echo "" echo ""
echo "Options:" echo "Options:"
echo " --verbose Show verbose output." echo " --verbose Show verbose output."
echo " -c|--channel <CHANNEL> The channel of KoreBuild to download. Overrides the value from the config file.." echo " -c|--channel <CHANNEL> The channel of KoreBuild to download. Overrides the value from the config file.."
echo " --config-file <FILE> The path to the configuration file that stores values. Defaults to korebuild.json." echo " --config-file <FILE> The path to the configuration file that stores values. Defaults to korebuild.json."
echo " -d|--dotnet-home <DIR> The directory where .NET Core tools will be stored. Defaults to '\$DOTNET_HOME' or '\$HOME/.dotnet." echo " -d|--dotnet-home <DIR> The directory where .NET Core tools will be stored. Defaults to '\$DOTNET_HOME' or '\$HOME/.dotnet."
echo " --path <PATH> The directory to build. Defaults to the directory containing the script." echo " --path <PATH> The directory to build. Defaults to the directory containing the script."
echo " -s|--tools-source|-ToolsSource <URL> The base url where build tools can be downloaded. Overrides the value from the config file." echo " -s|--tools-source|-ToolsSource <URL> The base url where build tools can be downloaded. Overrides the value from the config file."
echo " -u|--update Update to the latest KoreBuild even if the lock file is present." echo " --tools-source-suffix|-ToolsSourceSuffix <SUFFIX> The suffix to append to tools-source. Useful for query strings."
echo " -u|--update Update to the latest KoreBuild even if the lock file is present."
echo "" echo ""
echo "Description:" echo "Description:"
echo " This function will create a file \$DIR/korebuild-lock.txt. This lock file can be committed to source, but does not have to be." echo " This function will create a file \$DIR/korebuild-lock.txt. This lock file can be committed to source, but does not have to be."
@@ -50,7 +52,7 @@ get_korebuild() {
local version local version
local lock_file="$repo_path/korebuild-lock.txt" local lock_file="$repo_path/korebuild-lock.txt"
if [ ! -f "$lock_file" ] || [ "$update" = true ]; then if [ ! -f "$lock_file" ] || [ "$update" = true ]; then
__get_remote_file "$tools_source/korebuild/channels/$channel/latest.txt" "$lock_file" __get_remote_file "$tools_source/korebuild/channels/$channel/latest.txt" "$lock_file" "$tools_source_suffix"
fi fi
version="$(grep 'version:*' -m 1 "$lock_file")" version="$(grep 'version:*' -m 1 "$lock_file")"
if [[ "$version" == '' ]]; then if [[ "$version" == '' ]]; then
@@ -66,7 +68,7 @@ get_korebuild() {
local remote_path="$tools_source/korebuild/artifacts/$version/korebuild.$version.zip" local remote_path="$tools_source/korebuild/artifacts/$version/korebuild.$version.zip"
tmpfile="$(mktemp)" tmpfile="$(mktemp)"
echo -e "${MAGENTA}Downloading KoreBuild ${version}${RESET}" echo -e "${MAGENTA}Downloading KoreBuild ${version}${RESET}"
if __get_remote_file "$remote_path" "$tmpfile"; then if __get_remote_file "$remote_path" "$tmpfile" "$tools_source_suffix"; then
unzip -q -d "$korebuild_path" "$tmpfile" unzip -q -d "$korebuild_path" "$tmpfile"
fi fi
rm "$tmpfile" || true rm "$tmpfile" || true
@@ -98,6 +100,7 @@ __machine_has() {
__get_remote_file() { __get_remote_file() {
local remote_path=$1 local remote_path=$1
local local_path=$2 local local_path=$2
local remote_path_suffix=$3
if [[ "$remote_path" != 'http'* ]]; then if [[ "$remote_path" != 'http'* ]]; then
cp "$remote_path" "$local_path" cp "$remote_path" "$local_path"
@@ -106,14 +109,14 @@ __get_remote_file() {
local failed=false local failed=false
if __machine_has wget; then if __machine_has wget; then
wget --tries 10 --quiet -O "$local_path" "$remote_path" || failed=true wget --tries 10 --quiet -O "$local_path" "${remote_path}${remote_path_suffix}" || failed=true
else else
failed=true failed=true
fi fi
if [ "$failed" = true ] && __machine_has curl; then if [ "$failed" = true ] && __machine_has curl; then
failed=false failed=false
curl --retry 10 -sSL -f --create-dirs -o "$local_path" "$remote_path" || failed=true curl --retry 10 -sSL -f --create-dirs -o "$local_path" "${remote_path}${remote_path_suffix}" || failed=true
fi fi
if [ "$failed" = true ]; then if [ "$failed" = true ]; then
@@ -164,6 +167,11 @@ while [[ $# -gt 0 ]]; do
tools_source="${1:-}" tools_source="${1:-}"
[ -z "$tools_source" ] && __usage [ -z "$tools_source" ] && __usage
;; ;;
--tools-source-suffix|-ToolsSourceSuffix)
shift
tools_source_suffix="${1:-}"
[ -z "$tools_source_suffix" ] && __usage
;;
-u|--update|-Update) -u|--update|-Update)
update=true update=true
;; ;;

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks> <TargetFrameworks>netcoreapp2.1;net461</TargetFrameworks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<OutputType>exe</OutputType> <OutputType>exe</OutputType>
</PropertyGroup> </PropertyGroup>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks> <TargetFrameworks>netcoreapp2.1;net461</TargetFrameworks>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks> <TargetFrameworks>netcoreapp2.1;net461</TargetFrameworks>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>

View File

@@ -14,8 +14,7 @@ namespace Microsoft.AspNetCore.NodeServices.Sockets
/// <summary> /// <summary>
/// A specialisation of the OutOfProcessNodeInstance base class that uses a lightweight binary streaming protocol /// A specialisation of the OutOfProcessNodeInstance base class that uses a lightweight binary streaming protocol
/// to perform RPC invocations. The physical transport is Named Pipes on Windows, or Domain Sockets on Linux/Mac. /// to perform RPC invocations. The physical transport is Named Pipes on Windows, or Domain Sockets on Linux/Mac.
/// For details on the binary streaming protocol, see /// For details on the binary streaming protocol, see <see cref="Microsoft.AspNetCore.NodeServices.Sockets.VirtualConnections.VirtualConnectionClient" />
/// Microsoft.AspNetCore.NodeServices.HostingModels.VirtualConnections.VirtualConnectionClient.
/// The advantage versus using HTTP for RPC is that this is faster (not surprisingly - there's much less overhead /// The advantage versus using HTTP for RPC is that this is faster (not surprisingly - there's much less overhead
/// because we don't need most of the functionality of HTTP. /// because we don't need most of the functionality of HTTP.
/// ///
@@ -238,4 +237,4 @@ namespace Microsoft.AspNetCore.NodeServices.Sockets
} }
#pragma warning restore 649 #pragma warning restore 649
} }
} }

View File

@@ -20,7 +20,6 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli
public class AngularCliBuilder : ISpaPrerendererBuilder public class AngularCliBuilder : ISpaPrerendererBuilder
{ {
private static TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(5); // This is a development-time only feature, so a very long timeout is fine private static TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(5); // This is a development-time only feature, so a very long timeout is fine
private static TimeSpan BuildTimeout = TimeSpan.FromSeconds(50); // Note that the HTTP request itself by default times out after 60s, so you only get useful error information if this is shorter
private readonly string _npmScriptName; private readonly string _npmScriptName;
@@ -39,7 +38,7 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli
} }
/// <inheritdoc /> /// <inheritdoc />
public Task Build(ISpaBuilder spaBuilder) public async Task Build(ISpaBuilder spaBuilder)
{ {
var sourcePath = spaBuilder.Options.SourcePath; var sourcePath = spaBuilder.Options.SourcePath;
if (string.IsNullOrEmpty(sourcePath)) if (string.IsNullOrEmpty(sourcePath))
@@ -57,18 +56,26 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli
null); null);
npmScriptRunner.AttachToLogger(logger); npmScriptRunner.AttachToLogger(logger);
using (var stdOutReader = new EventedStreamStringReader(npmScriptRunner.StdOut))
using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr)) using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr))
{ {
try try
{ {
return npmScriptRunner.StdOut.WaitForMatch( await npmScriptRunner.StdOut.WaitForMatch(
new Regex("chunk", RegexOptions.None, RegexMatchTimeout), new Regex("Date", RegexOptions.None, RegexMatchTimeout));
BuildTimeout);
} }
catch (EndOfStreamException ex) catch (EndOfStreamException ex)
{ {
throw new InvalidOperationException( throw new InvalidOperationException(
$"The NPM script '{_npmScriptName}' exited without indicating success. " + $"The NPM script '{_npmScriptName}' exited without indicating success.\n" +
$"Output was: {stdOutReader.ReadAsString()}\n" +
$"Error output was: {stdErrReader.ReadAsString()}", ex);
}
catch (OperationCanceledException ex)
{
throw new InvalidOperationException(
$"The NPM script '{_npmScriptName}' timed out without indicating success. " +
$"Output was: {stdOutReader.ReadAsString()}\n" +
$"Error output was: {stdErrReader.ReadAsString()}", ex); $"Error output was: {stdErrReader.ReadAsString()}", ex);
} }
} }

View File

@@ -10,6 +10,9 @@ using System;
using System.IO; using System.IO;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Threading;
using System.Net.Http;
using Microsoft.AspNetCore.SpaServices.Extensions.Util;
namespace Microsoft.AspNetCore.SpaServices.AngularCli namespace Microsoft.AspNetCore.SpaServices.AngularCli
{ {
@@ -17,7 +20,6 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli
{ {
private const string LogCategoryName = "Microsoft.AspNetCore.SpaServices"; private const string LogCategoryName = "Microsoft.AspNetCore.SpaServices";
private static TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(5); // This is a development-time only feature, so a very long timeout is fine private static TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(5); // This is a development-time only feature, so a very long timeout is fine
private static TimeSpan StartupTimeout = TimeSpan.FromSeconds(50); // Note that the HTTP request itself by default times out after 60s, so you only get useful error information if this is shorter
public static void Attach( public static void Attach(
ISpaBuilder spaBuilder, ISpaBuilder spaBuilder,
@@ -47,7 +49,16 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli
var targetUriTask = angularCliServerInfoTask.ContinueWith( var targetUriTask = angularCliServerInfoTask.ContinueWith(
task => new UriBuilder("http", "localhost", task.Result.Port).Uri); task => new UriBuilder("http", "localhost", task.Result.Port).Uri);
SpaProxyingExtensions.UseProxyToSpaDevelopmentServer(spaBuilder, targetUriTask); SpaProxyingExtensions.UseProxyToSpaDevelopmentServer(spaBuilder, () =>
{
// On each request, we create a separate startup task with its own timeout. That way, even if
// the first request times out, subsequent requests could still work.
var timeout = spaBuilder.Options.StartupTimeout;
return targetUriTask.WithTimeout(timeout,
$"The Angular CLI process did not start listening for requests " +
$"within the timeout period of {timeout.Seconds} seconds. " +
$"Check the log output for error information.");
});
} }
private static async Task<AngularCliServerInfo> StartAngularCliServerAsync( private static async Task<AngularCliServerInfo> StartAngularCliServerAsync(
@@ -66,8 +77,7 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli
try try
{ {
openBrowserLine = await npmScriptRunner.StdOut.WaitForMatch( openBrowserLine = await npmScriptRunner.StdOut.WaitForMatch(
new Regex("open your browser on (http\\S+)", RegexOptions.None, RegexMatchTimeout), new Regex("open your browser on (http\\S+)", RegexOptions.None, RegexMatchTimeout));
StartupTimeout);
} }
catch (EndOfStreamException ex) catch (EndOfStreamException ex)
{ {
@@ -76,26 +86,45 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli
$"Angular CLI was listening for requests. The error output was: " + $"Angular CLI was listening for requests. The error output was: " +
$"{stdErrReader.ReadAsString()}", ex); $"{stdErrReader.ReadAsString()}", ex);
} }
catch (TaskCanceledException ex)
{
throw new InvalidOperationException(
$"The Angular CLI process did not start listening for requests " +
$"within the timeout period of {StartupTimeout.Seconds} seconds. " +
$"Check the log output for error information.", ex);
}
} }
var uri = new Uri(openBrowserLine.Groups[1].Value); var uri = new Uri(openBrowserLine.Groups[1].Value);
var serverInfo = new AngularCliServerInfo { Port = uri.Port }; var serverInfo = new AngularCliServerInfo { Port = uri.Port };
// Even after the Angular CLI claims to be listening for requests, there's a short // Even after the Angular CLI claims to be listening for requests, there's a short
// period where it will give an error if you make a request too quickly. Give it // period where it will give an error if you make a request too quickly
// a moment to finish starting up. await WaitForAngularCliServerToAcceptRequests(uri);
await Task.Delay(500);
return serverInfo; return serverInfo;
} }
private static async Task WaitForAngularCliServerToAcceptRequests(Uri cliServerUri)
{
// To determine when it's actually ready, try making HEAD requests to '/'. If it
// produces any HTTP response (even if it's 404) then it's ready. If it rejects the
// connection then it's not ready. We keep trying forever because this is dev-mode
// only, and only a single startup attempt will be made, and there's a further level
// of timeouts enforced on a per-request basis.
using (var client = new HttpClient())
{
while (true)
{
try
{
// If we get any HTTP response, the CLI server is ready
await client.SendAsync(
new HttpRequestMessage(HttpMethod.Head, cliServerUri),
new CancellationTokenSource(1000).Token);
return;
}
catch (Exception)
{
await Task.Delay(1000); // 1 second
}
}
}
}
class AngularCliServerInfo class AngularCliServerInfo
{ {
public int Port { get; set; } public int Port { get; set; }

View File

@@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.NodeServices; using Microsoft.AspNetCore.NodeServices;
using Microsoft.AspNetCore.SpaServices; using Microsoft.AspNetCore.SpaServices;
using Microsoft.AspNetCore.SpaServices.Extensions.Util;
using Microsoft.AspNetCore.SpaServices.Prerendering; using Microsoft.AspNetCore.SpaServices.Prerendering;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
@@ -68,6 +69,7 @@ namespace Microsoft.AspNetCore.Builder
var excludePathStrings = (options.ExcludeUrls ?? Array.Empty<string>()) var excludePathStrings = (options.ExcludeUrls ?? Array.Empty<string>())
.Select(url => new PathString(url)) .Select(url => new PathString(url))
.ToArray(); .ToArray();
var buildTimeout = spaBuilder.Options.StartupTimeout;
applicationBuilder.Use(async (context, next) => applicationBuilder.Use(async (context, next) =>
{ {
@@ -85,9 +87,15 @@ namespace Microsoft.AspNetCore.Builder
} }
// If we're building on demand, wait for that to finish, or raise any build errors // If we're building on demand, wait for that to finish, or raise any build errors
if (buildOnDemandTask != null) if (buildOnDemandTask != null && !buildOnDemandTask.IsCompleted)
{ {
await buildOnDemandTask; // For better debuggability, create a per-request timeout that makes it clear if the
// prerendering builder took too long for this request, but without aborting the
// underlying build task so that subsequent requests could still work.
await buildOnDemandTask.WithTimeout(buildTimeout,
$"The prerendering build process did not complete within the " +
$"timeout period of {buildTimeout.Seconds} seconds. " +
$"Check the log output for error information.");
} }
// It's no good if we try to return a 304. We need to capture the actual // It's no good if we try to return a 304. We need to capture the actual
@@ -225,7 +233,8 @@ namespace Microsoft.AspNetCore.Builder
if (!string.IsNullOrEmpty(renderResult.RedirectUrl)) if (!string.IsNullOrEmpty(renderResult.RedirectUrl))
{ {
context.Response.Redirect(renderResult.RedirectUrl); var permanentRedirect = renderResult.StatusCode.GetValueOrDefault() == 301;
context.Response.Redirect(renderResult.RedirectUrl, permanentRedirect);
} }
else else
{ {
@@ -239,6 +248,11 @@ namespace Microsoft.AspNetCore.Builder
$"embed any information you wish to return to the client."); $"embed any information you wish to return to the client.");
} }
if (renderResult.StatusCode.HasValue)
{
context.Response.StatusCode = renderResult.StatusCode.Value;
}
context.Response.ContentType = "text/html"; context.Response.ContentType = "text/html";
await context.Response.WriteAsync(renderResult.Html); await context.Response.WriteAsync(renderResult.Html);
} }

View File

@@ -45,7 +45,7 @@ namespace Microsoft.AspNetCore.Builder
{ {
UseProxyToSpaDevelopmentServer( UseProxyToSpaDevelopmentServer(
spaBuilder, spaBuilder,
Task.FromResult(baseUri)); () => Task.FromResult(baseUri));
} }
/// <summary> /// <summary>
@@ -54,10 +54,10 @@ namespace Microsoft.AspNetCore.Builder
/// development. Do not enable this middleware in production applications. /// development. Do not enable this middleware in production applications.
/// </summary> /// </summary>
/// <param name="spaBuilder">The <see cref="ISpaBuilder"/>.</param> /// <param name="spaBuilder">The <see cref="ISpaBuilder"/>.</param>
/// <param name="baseUriTask">A <see cref="Task"/> that resolves with the target base URI to which requests should be proxied.</param> /// <param name="baseUriTaskFactory">A callback that will be invoked on each request to supply a <see cref="Task"/> that resolves with the target base URI to which requests should be proxied.</param>
public static void UseProxyToSpaDevelopmentServer( public static void UseProxyToSpaDevelopmentServer(
this ISpaBuilder spaBuilder, this ISpaBuilder spaBuilder,
Task<Uri> baseUriTask) Func<Task<Uri>> baseUriTaskFactory)
{ {
var applicationBuilder = spaBuilder.ApplicationBuilder; var applicationBuilder = spaBuilder.ApplicationBuilder;
var applicationStoppingToken = GetStoppingToken(applicationBuilder); var applicationStoppingToken = GetStoppingToken(applicationBuilder);
@@ -72,11 +72,11 @@ namespace Microsoft.AspNetCore.Builder
var neverTimeOutHttpClient = var neverTimeOutHttpClient =
SpaProxy.CreateHttpClientForProxy(Timeout.InfiniteTimeSpan); SpaProxy.CreateHttpClientForProxy(Timeout.InfiniteTimeSpan);
// Proxy all requests into the Angular CLI server // Proxy all requests to the SPA development server
applicationBuilder.Use(async (context, next) => applicationBuilder.Use(async (context, next) =>
{ {
var didProxyRequest = await SpaProxy.PerformProxyRequest( var didProxyRequest = await SpaProxy.PerformProxyRequest(
context, neverTimeOutHttpClient, baseUriTask, applicationStoppingToken, context, neverTimeOutHttpClient, baseUriTaskFactory(), applicationStoppingToken,
proxy404s: true); proxy404s: true);
}); });
} }

View File

@@ -11,6 +11,7 @@ using System.IO;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.SpaServices.Extensions.Util;
namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer
{ {
@@ -18,7 +19,6 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer
{ {
private const string LogCategoryName = "Microsoft.AspNetCore.SpaServices"; private const string LogCategoryName = "Microsoft.AspNetCore.SpaServices";
private static TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(5); // This is a development-time only feature, so a very long timeout is fine private static TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(5); // This is a development-time only feature, so a very long timeout is fine
private static TimeSpan StartupTimeout = TimeSpan.FromSeconds(50); // Note that the HTTP request itself by default times out after 60s, so you only get useful error information if this is shorter
public static void Attach( public static void Attach(
ISpaBuilder spaBuilder, ISpaBuilder spaBuilder,
@@ -48,7 +48,16 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer
var targetUriTask = portTask.ContinueWith( var targetUriTask = portTask.ContinueWith(
task => new UriBuilder("http", "localhost", task.Result).Uri); task => new UriBuilder("http", "localhost", task.Result).Uri);
SpaProxyingExtensions.UseProxyToSpaDevelopmentServer(spaBuilder, targetUriTask); SpaProxyingExtensions.UseProxyToSpaDevelopmentServer(spaBuilder, () =>
{
// On each request, we create a separate startup task with its own timeout. That way, even if
// the first request times out, subsequent requests could still work.
var timeout = spaBuilder.Options.StartupTimeout;
return targetUriTask.WithTimeout(timeout,
$"The create-react-app server did not start listening for requests " +
$"within the timeout period of {timeout.Seconds} seconds. " +
$"Check the log output for error information.");
});
} }
private static async Task<int> StartCreateReactAppServerAsync( private static async Task<int> StartCreateReactAppServerAsync(
@@ -75,8 +84,7 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer
// no compiler warnings. So instead of waiting for that, consider it ready as soon // no compiler warnings. So instead of waiting for that, consider it ready as soon
// as it starts listening for requests. // as it starts listening for requests.
await npmScriptRunner.StdOut.WaitForMatch( await npmScriptRunner.StdOut.WaitForMatch(
new Regex("Starting the development server", RegexOptions.None, RegexMatchTimeout), new Regex("Starting the development server", RegexOptions.None, RegexMatchTimeout));
StartupTimeout);
} }
catch (EndOfStreamException ex) catch (EndOfStreamException ex)
{ {
@@ -85,13 +93,6 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer
$"create-react-app server was listening for requests. The error output was: " + $"create-react-app server was listening for requests. The error output was: " +
$"{stdErrReader.ReadAsString()}", ex); $"{stdErrReader.ReadAsString()}", ex);
} }
catch (TaskCanceledException ex)
{
throw new InvalidOperationException(
$"The create-react-app server did not start listening for requests " +
$"within the timeout period of {StartupTimeout.Seconds} seconds. " +
$"Check the log output for error information.", ex);
}
} }
return portNumber; return portNumber;

View File

@@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.SpaServices
// Developers who need to host more than one SPA with distinct default pages can // Developers who need to host more than one SPA with distinct default pages can
// override the file provider // override the file provider
app.UseSpaStaticFilesInternal( app.UseSpaStaticFilesInternal(
overrideFileProvider: options.DefaultPageFileProvider, options.DefaultPageStaticFileOptions ?? new StaticFileOptions(),
allowFallbackOnServingWebRootFiles: true); allowFallbackOnServingWebRootFiles: true);
// If the default file didn't get served as a static file (usually because it was not // If the default file didn't get served as a static file (usually because it was not

View File

@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
@@ -29,7 +30,7 @@ namespace Microsoft.AspNetCore.SpaServices
internal SpaOptions(SpaOptions copyFromOptions) internal SpaOptions(SpaOptions copyFromOptions)
{ {
_defaultPage = copyFromOptions.DefaultPage; _defaultPage = copyFromOptions.DefaultPage;
DefaultPageFileProvider = copyFromOptions.DefaultPageFileProvider; DefaultPageStaticFileOptions = copyFromOptions.DefaultPageStaticFileOptions;
SourcePath = copyFromOptions.SourcePath; SourcePath = copyFromOptions.SourcePath;
} }
@@ -52,14 +53,14 @@ namespace Microsoft.AspNetCore.SpaServices
} }
/// <summary> /// <summary>
/// Gets or sets the <see cref="IFileProvider"/> that supplies content /// Gets or sets the <see cref="StaticFileOptions"/> that supplies content
/// for serving the SPA's default page. /// for serving the SPA's default page.
/// ///
/// If not set, a default file provider will read files from the /// If not set, a default file provider will read files from the
/// <see cref="IHostingEnvironment.WebRootPath"/>, which by default is /// <see cref="IHostingEnvironment.WebRootPath"/>, which by default is
/// the <c>wwwroot</c> directory. /// the <c>wwwroot</c> directory.
/// </summary> /// </summary>
public IFileProvider DefaultPageFileProvider { get; set; } public StaticFileOptions DefaultPageStaticFileOptions { get; set; }
/// <summary> /// <summary>
/// Gets or sets the path, relative to the application working directory, /// Gets or sets the path, relative to the application working directory,
@@ -67,5 +68,11 @@ namespace Microsoft.AspNetCore.SpaServices
/// development. The directory may not exist in published applications. /// development. The directory may not exist in published applications.
/// </summary> /// </summary>
public string SourcePath { get; set; } public string SourcePath { get; set; }
/// <summary>
/// Gets or sets the maximum duration that a request will wait for the SPA
/// to become ready to serve to the client.
/// </summary>
public TimeSpan StartupTimeout { get; set; } = TimeSpan.FromSeconds(50);
} }
} }

View File

@@ -50,53 +50,75 @@ namespace Microsoft.Extensions.DependencyInjection
/// </summary> /// </summary>
/// <param name="applicationBuilder">The <see cref="IApplicationBuilder"/>.</param> /// <param name="applicationBuilder">The <see cref="IApplicationBuilder"/>.</param>
public static void UseSpaStaticFiles(this IApplicationBuilder applicationBuilder) public static void UseSpaStaticFiles(this IApplicationBuilder applicationBuilder)
{
UseSpaStaticFiles(applicationBuilder, new StaticFileOptions());
}
/// <summary>
/// Configures the application to serve static files for a Single Page Application (SPA).
/// The files will be located using the registered <see cref="ISpaStaticFileProvider"/> service.
/// </summary>
/// <param name="applicationBuilder">The <see cref="IApplicationBuilder"/>.</param>
/// <param name="options">Specifies options for serving the static files.</param>
public static void UseSpaStaticFiles(this IApplicationBuilder applicationBuilder, StaticFileOptions options)
{ {
if (applicationBuilder == null) if (applicationBuilder == null)
{ {
throw new ArgumentNullException(nameof(applicationBuilder)); throw new ArgumentNullException(nameof(applicationBuilder));
} }
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
UseSpaStaticFilesInternal(applicationBuilder, UseSpaStaticFilesInternal(applicationBuilder,
overrideFileProvider: null, staticFileOptions: options,
allowFallbackOnServingWebRootFiles: false); allowFallbackOnServingWebRootFiles: false);
} }
internal static void UseSpaStaticFilesInternal( internal static void UseSpaStaticFilesInternal(
this IApplicationBuilder app, this IApplicationBuilder app,
IFileProvider overrideFileProvider, StaticFileOptions staticFileOptions,
bool allowFallbackOnServingWebRootFiles) bool allowFallbackOnServingWebRootFiles)
{ {
var shouldServeStaticFiles = ShouldServeStaticFiles( if (staticFileOptions == null)
app,
overrideFileProvider,
allowFallbackOnServingWebRootFiles,
out var fileProviderOrDefault);
if (shouldServeStaticFiles)
{ {
app.UseStaticFiles(new StaticFileOptions throw new ArgumentNullException(nameof(staticFileOptions));
{
FileProvider = fileProviderOrDefault
});
} }
// If the file provider was explicitly supplied, that takes precedence over any other
// configured file provider. This is most useful if the application hosts multiple SPAs
// (via multiple calls to UseSpa()), so each needs to serve its own separate static files
// instead of using AddSpaStaticFiles/UseSpaStaticFiles.
// But if no file provider was specified, try to get one from the DI config.
if (staticFileOptions.FileProvider == null)
{
var shouldServeStaticFiles = ShouldServeStaticFiles(
app,
allowFallbackOnServingWebRootFiles,
out var fileProviderOrDefault);
if (shouldServeStaticFiles)
{
staticFileOptions.FileProvider = fileProviderOrDefault;
}
else
{
// The registered ISpaStaticFileProvider says we shouldn't
// serve static files
return;
}
}
app.UseStaticFiles(staticFileOptions);
} }
private static bool ShouldServeStaticFiles( private static bool ShouldServeStaticFiles(
IApplicationBuilder app, IApplicationBuilder app,
IFileProvider overrideFileProvider,
bool allowFallbackOnServingWebRootFiles, bool allowFallbackOnServingWebRootFiles,
out IFileProvider fileProviderOrDefault) out IFileProvider fileProviderOrDefault)
{ {
if (overrideFileProvider != null)
{
// If the file provider was explicitly supplied, that takes precedence over any other
// configured file provider. This is most useful if the application hosts multiple SPAs
// (via multiple calls to UseSpa()), so each needs to serve its own separate static files
// instead of using AddSpaStaticFiles/UseSpaStaticFiles.
fileProviderOrDefault = overrideFileProvider;
return true;
}
var spaStaticFilesService = app.ApplicationServices.GetService<ISpaStaticFileProvider>(); var spaStaticFilesService = app.ApplicationServices.GetService<ISpaStaticFileProvider>();
if (spaStaticFilesService != null) if (spaStaticFilesService != null)
{ {

View File

@@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.NodeServices.Util
Task.Factory.StartNew(Run); Task.Factory.StartNew(Run);
} }
public Task<Match> WaitForMatch(Regex regex, TimeSpan timeout = default) public Task<Match> WaitForMatch(Regex regex)
{ {
var tcs = new TaskCompletionSource<Match>(); var tcs = new TaskCompletionSource<Match>();
var completionLock = new object(); var completionLock = new object();
@@ -72,15 +72,6 @@ namespace Microsoft.AspNetCore.NodeServices.Util
OnReceivedLine += onReceivedLineHandler; OnReceivedLine += onReceivedLineHandler;
OnStreamClosed += onStreamClosedHandler; OnStreamClosed += onStreamClosedHandler;
if (timeout != default)
{
var timeoutToken = new CancellationTokenSource(timeout);
timeoutToken.Token.Register(() =>
{
ResolveIfStillPending(() => tcs.SetCanceled());
});
}
return tcs.Task; return tcs.Task;
} }

View File

@@ -0,0 +1,35 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.SpaServices.Extensions.Util
{
internal static class TaskTimeoutExtensions
{
public static async Task WithTimeout(this Task task, TimeSpan timeoutDelay, string message)
{
if (task == await Task.WhenAny(task, Task.Delay(timeoutDelay)))
{
task.Wait(); // Allow any errors to propagate
}
else
{
throw new TimeoutException(message);
}
}
public static async Task<T> WithTimeout<T>(this Task<T> task, TimeSpan timeoutDelay, string message)
{
if (task == await Task.WhenAny(task, Task.Delay(timeoutDelay)))
{
return task.Result;
}
else
{
throw new TimeoutException(message);
}
}
}
}

View File

@@ -561,7 +561,7 @@ Typically, when you change a source file, the effects appear in your local brows
First ensure you already have a working Webpack dev middleware setup. Then, install the `webpack-hot-middleware` NPM module: First ensure you already have a working Webpack dev middleware setup. Then, install the `webpack-hot-middleware` NPM module:
``` ```
npm install --save webpack-hot-middleware npm install --save-dev webpack-hot-middleware
``` ```
At the top of your `Startup.cs` file, add the following namespace reference: At the top of your `Startup.cs` file, add the following namespace reference:
@@ -620,7 +620,7 @@ app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions {
Also, install the NPM module `aspnet-webpack-react`, e.g.: Also, install the NPM module `aspnet-webpack-react`, e.g.:
``` ```
npm install --save aspnet-webpack-react npm install --save-dev aspnet-webpack-react
``` ```
Now if you edit any React component (e.g., in `.jsx` or `.tsx` files), the updated component will be injected into the running application, and will even preserve its in-memory state. Now if you edit any React component (e.g., in `.jsx` or `.tsx` files), the updated component will be injected into the running application, and will even preserve its in-memory state.