Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions .github/workflows/e2e_samples.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
name: E2E - Sample Hosts

# Runs the Playwright-based CrestApps.Core.Tests.Samples suite against the MVC and Blazor sample hosts.
# This workflow is intentionally separate from the PR/main CI pipelines so e2e cost and flakiness do not
# block routine pull requests. It runs nightly and can also be triggered manually from the Actions tab.

on:
workflow_dispatch:
schedule:
# 07:00 UTC daily (~03:00 ET / midnight PT) — chosen to land before the start of the workday.
- cron: '0 7 * * *'

permissions:
contents: read

concurrency:
group: e2e-samples-${{ github.ref }}
cancel-in-progress: true

env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
ASPNETCORE_ENVIRONMENT: Development
CRESTAPPS_MVC_BASE_URL: http://localhost:5101
CRESTAPPS_BLAZOR_BASE_URL: http://localhost:5201

jobs:
e2e:
name: E2E - Playwright
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6

- uses: actions/setup-dotnet@v5
with:
dotnet-version: |
10.0.x

- name: Build solution
run: |
dotnet build ./CrestApps.Core.slnx -c Release /p:RunAnalyzers=true /p:NuGetAudit=false

- name: Install Playwright browsers (Chromium)
run: |
pwsh ./tests/CrestApps.Core.Tests.Samples/bin/Release/net10.0/playwright.ps1 install --with-deps chromium

- name: Start MVC sample host
run: |
mkdir -p ./artifacts/e2e-logs
nohup dotnet run --no-build -c Release \
--project ./src/Startup/CrestApps.Core.Mvc.Web/CrestApps.Core.Mvc.Web.csproj \
--urls "http://localhost:5101" \
> ./artifacts/e2e-logs/mvc.log 2>&1 &
echo "MVC_PID=$!" >> "$GITHUB_ENV"

- name: Start Blazor sample host
run: |
nohup dotnet run --no-build -c Release \
--project ./src/Startup/CrestApps.Core.Blazor.Web/CrestApps.Core.Blazor.Web.csproj \
--urls "http://localhost:5201" \
> ./artifacts/e2e-logs/blazor.log 2>&1 &
echo "BLAZOR_PID=$!" >> "$GITHUB_ENV"

- name: Wait for sample hosts to become reachable
run: |
set -euo pipefail
wait_for() {
local name="$1"; local url="$2"; local tries=60
for i in $(seq 1 "$tries"); do
if curl -fsS --max-time 5 "$url" > /dev/null; then
echo "$name reachable after ${i}s ($url)"
return 0
fi
sleep 2
done
echo "::error::$name did not become reachable at $url within $((tries * 2))s"
return 1
}
wait_for "MVC" "$CRESTAPPS_MVC_BASE_URL/Account/Login"
wait_for "Blazor" "$CRESTAPPS_BLAZOR_BASE_URL/account/login"

- name: Run E2E sample tests
run: |
dotnet test ./tests/CrestApps.Core.Tests.Samples/CrestApps.Core.Tests.Samples.csproj \
-c Release --no-build \
--logger "trx;LogFileName=samples.trx" \
--results-directory ./artifacts/e2e-results

- name: Stop sample hosts
if: always()
run: |
for pid in "${MVC_PID:-}" "${BLAZOR_PID:-}"; do
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
kill "$pid" || true
fi
done

- name: Upload e2e logs and results
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-samples-artifacts
path: |
./artifacts/e2e-logs/**
./artifacts/e2e-results/**
if-no-files-found: warn
26 changes: 25 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@

<PropertyGroup>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix>preview</VersionSuffix>
<VersionSuffix></VersionSuffix>
<VersionSuffix Condition="'$(VersionSuffix)' != '' AND '$(BuildNumber)' != ''">$(VersionSuffix)-$(BuildNumber)</VersionSuffix>
<InformationalVersion></InformationalVersion>
</PropertyGroup>
Expand All @@ -56,6 +56,22 @@
<AccelerateBuildsInVisualStudio>true</AccelerateBuildsInVisualStudio>
</PropertyGroup>

<PropertyGroup Label="Build hygiene">
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>

<PropertyGroup Label="Reproducible build">
<Deterministic>true</Deterministic>
<ContinuousIntegrationBuild Condition="'$(GITHUB_ACTIONS)' == 'true' OR '$(TF_BUILD)' == 'true' OR '$(ContinuousIntegrationBuild)' == 'true'">true</ContinuousIntegrationBuild>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>

<ItemGroup Condition="'$(IsPackable)' == 'true' AND '$(IsTestProject)' != 'true'">
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
</ItemGroup>

<PropertyGroup Label="Analysis rules">

<AnalysisLevel>latest-Recommended</AnalysisLevel>
Expand Down Expand Up @@ -111,6 +127,14 @@
<!-- NU1605: NuGet Warning NU1605 -->
<NoWarn>$(NoWarn);NU1605</NoWarn>

<!-- NU5104: A stable release of a package should not have a prerelease dependency.
CrestApps.Core 1.0.0 ships against .NET 10 preview ecosystem packages (notably
Microsoft.Extensions.DataIngestion, A2A.AspNetCore, Lucene.Net.Analysis.Common)
that are still pre-release at the time of this release. The 1.0.0 release notes
document this explicitly. Re-enable this warning once those upstream packages ship
stable releases. -->
<NoWarn>$(NoWarn);NU5104</NoWarn>

<NoWarn>$(NoWarn),1573,1591,1712</NoWarn>

</PropertyGroup>
Expand Down
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<PackageVersion Include="YesSql.Core" Version="5.4.7" />
<PackageVersion Include="YesSql.Provider.Sqlite" Version="5.4.7" />
<PackageVersion Include="ZString" Version="2.6.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<!-- Copilot Packages -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ public interface IOrchestrationContextBuilder
/// An optional delegate to override or fine-tune the context after handlers have run
/// </param>
/// <c>BuildingAsync</c> but before <c>BuiltAsync</c>.
/// <param name="cancellationToken">The cancellation token for the build operation.</param>
/// </param>
/// <returns>A task that completes with the fully built <see cref="OrchestrationContext"/>.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="resource"/> is <see langword="null"/>.</exception>
ValueTask<OrchestrationContext> BuildAsync(object resource, Action<OrchestrationContext> configure = null);
ValueTask<OrchestrationContext> BuildAsync(object resource, Action<OrchestrationContext> configure = null, CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ public interface IOrchestrationContextBuilderHandler
/// configuration delegate is applied.
/// </summary>
/// <param name="context">Carries both the source resource and the mutable <see cref="OrchestrationContext"/>.</param>
/// <param name="cancellationToken">The cancellation token for the build operation.</param>
/// <returns>A task that completes when the mutation or validation is done.</returns>
Task BuildingAsync(OrchestrationContextBuildingContext context);
Task BuildingAsync(OrchestrationContextBuildingContext context, CancellationToken cancellationToken = default);

/// <summary>
/// Called after the context has been fully constructed and the optional caller configuration delegate
/// has been applied.
/// </summary>
/// <param name="context">Carries the final <see cref="OrchestrationContext"/> along with the source resource.</param>
/// <param name="cancellationToken">The cancellation token for the build operation.</param>
/// <returns>A task that completes when post-build processing is done.</returns>
Task BuiltAsync(OrchestrationContextBuiltContext context);
Task BuiltAsync(OrchestrationContextBuiltContext context, CancellationToken cancellationToken = default);
}
3 changes: 3 additions & 0 deletions src/CrestApps.Core.Docs/docs/changelog/v1.0.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ description: Initial standalone release notes for the CrestApps.Core repository.
- centralizes reusable MCP runtime registration in `AddCoreAIMcpServices()`, moves the shared MCP metadata, capability-resolution, tool-registry, SSE settings-handler, and invoke-function services into `CrestApps.Core.AI.Mcp`, and splits optional StdIO transport registration so hosts can enable it only where needed
- standardizes A2A and MCP connection authentication on the shared `ClientAuthenticationType` enum, removes the protocol-specific duplicate enums, and adds an `AzureOpenAIClientMarker` so Azure OpenAI can participate in the same provider-marker conventions as the other AI clients without changing current runtime behavior
- treats aborted and canceled request-stream failures in the Aspire AppHost as observed task exceptions so local development no longer floods the console with benign unobserved-task noise
- keeps the MVC and Blazor sample hosts writing runtime uploads and other mutable files into each project's own `App_Data` folder while switching their `.NET 10` watch exclusions to the documented `**/App_Data/**` glob so Visual Studio Aspire runs do not restart when chat document uploads create files under `App_Data/Documents`
- generates external `.map` source map files for all JS and CSS assets in the gulp build pipeline, copies them into `dist/` during npm package preparation, and includes them in the `@crestapps/ai-chat-ui` package exports
- adds per-message text-to-speech play/pause controls on assistant messages in the AI Chat and Chat Interaction UIs, keeps the action toolbar pinned to the bottom-right of each response without reserving a separate action row, automatically stops other message players before starting a new one, and hides manual playback controls during Conversation mode
- renders sample-host `[doc:n]` citations as superscript markers and shows the resolved document links below each cited assistant response in both the MVC and Blazor chat UIs
Expand All @@ -52,4 +53,6 @@ description: Initial standalone release notes for the CrestApps.Core repository.
- registers shared indexing services in the framework by default, including `ISearchIndexProfileManager`, `ISearchIndexProfileProvisioningService`, and a null fallback `ISearchIndexProfileStore`, so hosts only need `.AddIndexingServices(...).AddYesSqlStores()` or `.AddEntityCoreStores()` when they want persisted index profile records
- registers `IAIProfileStore` in the shared AI services layer with a null fallback, and replaces it with provider-backed EntityCore or YesSql stores when AI services data stores are enabled so downstream services can always resolve the profile store
- keeps the MVC and Blazor sample-host AI profile, template, and chat-edit screens usable when Claude is not configured by treating failed Claude options validation as "provider unavailable" instead of crashing the page, and removes the legacy memory-settings compatibility shim so profile/template memory state now flows only through `MemoryMetadata`
- keeps the MVC and Blazor sample-host index profile editors aligned with deployment-name-based indexing by posting embedding deployment names instead of catalog IDs and by accepting either selector during embedding profile validation
- fixes sample-host content-root resolution when MVC or Blazor are launched through the Aspire AppHost so `App_Data\appsettings.json` and related local sample assets still load from the web-project directory instead of an Aspire output folder fallback
- updates the shared A2A and MCP sample clients so one client app can target either the MVC or Blazor sample host through a built-in server selector, and wires the Aspire AppHost to advertise both endpoints to those samples
2 changes: 2 additions & 0 deletions src/CrestApps.Core.Docs/docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ Aspire manages containers for services like Redis. You need a container runtime
dotnet run --project .\src\Startup\CrestApps.Core.Aspire.AppHost\CrestApps.Core.Aspire.AppHost.csproj
```

The MVC and Blazor sample hosts both keep their writable runtime state inside each project's own `App_Data` folder. Their project files exclude `**/App_Data/**` from `.NET 10` watch discovery so Visual Studio Aspire runs do not restart the hosted app when uploads, logs, or local SQLite files change at runtime.

## Smallest useful app integration

Use the `AddCrestAppsCore(...)` builder as the main entry point:
Expand Down
Loading
Loading