diff --git a/.codex/environments/environment.toml b/.codex/environments/environment.toml index 8b525d054a..273aa1d088 100644 --- a/.codex/environments/environment.toml +++ b/.codex/environments/environment.toml @@ -22,7 +22,7 @@ worktree_path="$PWD" echo "Setting up Exceptionless Aspire worktree: $worktree_path" if ! command -v dotnet >/dev/null 2>&1; then - echo "dotnet is not installed or not on PATH. Exceptionless requires .NET 10." >&2 + echo "dotnet is not installed or not on PATH. Exceptionless requires .NET 11." >&2 exit 1 fi diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f94530ef5f..44d5abd548 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -66,7 +66,7 @@ "version": "latest" }, "ghcr.io/devcontainers/features/dotnet:2": { - "version": "10.0" + "version": "11.0-preview" }, "ghcr.io/devcontainers/features/node:1": { "nodeGypDependencies": true, diff --git a/.github/workflows/build-arm64.yml b/.github/workflows/build-arm64.yml index 9bde92484d..e01c7e4a35 100644 --- a/.github/workflows/build-arm64.yml +++ b/.github/workflows/build-arm64.yml @@ -25,7 +25,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v6 with: - dotnet-version: 10.0.402 + dotnet-version: 11.0.100-rc.2.26504.105 - name: Build Reason env: GITHUB_EVENT: ${{ toJson(github) }} diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index f4c90bd753..ec19410c08 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -114,7 +114,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v6 with: - dotnet-version: 10.0.402 + dotnet-version: 11.0.100-rc.2.26504.105 - name: Version id: version @@ -156,7 +156,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v6 with: - dotnet-version: 10.0.402 + dotnet-version: 11.0.100-rc.2.26504.105 - uses: actions/cache@v6 with: @@ -215,7 +215,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v6 with: - dotnet-version: 10.0.402 + dotnet-version: 11.0.100-rc.2.26504.105 - name: Merge coverage run: | @@ -327,8 +327,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v6 with: - dotnet-version: 10.0.402 - dotnet-quality: ga + dotnet-version: 11.0.100-rc.2.26504.105 - name: Install Aspire CLI run: | diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index f534b8dd1c..d7ea3b9388 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -25,7 +25,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v6 with: - dotnet-version: 10.0.402 + dotnet-version: 11.0.100-rc.2.26504.105 - name: Install Aspire CLI run: | diff --git a/.github/workflows/elasticsearch-docker-8.yml b/.github/workflows/elasticsearch-docker-8.yml index 29da4456db..00c911eb5f 100644 --- a/.github/workflows/elasticsearch-docker-8.yml +++ b/.github/workflows/elasticsearch-docker-8.yml @@ -16,7 +16,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v6 with: - dotnet-version: 10.0.402 + dotnet-version: 11.0.100-rc.2.26504.105 - name: Build Reason env: GITHUB_EVENT: ${{ toJson(github) }} diff --git a/.vscode/launch.json b/.vscode/launch.json index b467cd8048..7fa3e5f201 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,7 +12,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "build", - "program": "${workspaceFolder}/src/Exceptionless.Web/bin/Debug/net10.0/Exceptionless.Web.dll", + "program": "${workspaceFolder}/src/Exceptionless.Web/bin/Debug/net11.0/Exceptionless.Web.dll", "args": [], "cwd": "${workspaceFolder}/src/Exceptionless.Web", "stopAtEntry": false, @@ -29,7 +29,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "build", - "program": "${workspaceFolder}/src/Exceptionless.Job/bin/Debug/net10.0/Exceptionless.Job.dll", + "program": "${workspaceFolder}/src/Exceptionless.Job/bin/Debug/net11.0/Exceptionless.Job.dll", "args": [], "cwd": "${workspaceFolder}", "stopAtEntry": false, diff --git a/AGENTS.md b/AGENTS.md index 1ec20f7afa..23915901a2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # Exceptionless -Real-time error monitoring platform on .NET 10, Aspire, and Svelte 5. Keep changes readable, narrow, and backwards compatible. +Real-time error monitoring platform on .NET 11, Aspire, and Svelte 5. Keep changes readable, narrow, and backwards compatible. This file is root-scope agent guidance. When working in a subtree, read any nested `AGENTS.md` there too. Prefer nearby repo skills for detailed backend, frontend, testing, repository, and dogfood workflows. diff --git a/Dockerfile b/Dockerfile index 2e4084f47d..1f578ab187 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:10.0.402 AS build +FROM mcr.microsoft.com/dotnet/sdk:11.0.100-rc.2 AS build ARG MinVerVersionOverride WORKDIR /app @@ -40,7 +40,7 @@ RUN dotnet publish -c Release -o out --no-build # job -FROM mcr.microsoft.com/dotnet/aspnet:10.0.13 AS job +FROM mcr.microsoft.com/dotnet/aspnet:11.0.0-rc.2 AS job WORKDIR /app COPY --from=job-publish /app/src/Exceptionless.Job/out ./ @@ -59,7 +59,7 @@ RUN dotnet publish -c Release -o out --no-build /p:SkipSpaPublish=true # api -FROM mcr.microsoft.com/dotnet/aspnet:10.0.13 AS api +FROM mcr.microsoft.com/dotnet/aspnet:11.0.0-rc.2 AS api WORKDIR /app COPY --from=api-publish /app/src/Exceptionless.Web/out ./ @@ -77,7 +77,7 @@ RUN dotnet publish -c Release -o out --no-build # app -FROM mcr.microsoft.com/dotnet/aspnet:10.0.13 AS app +FROM mcr.microsoft.com/dotnet/aspnet:11.0.0-rc.2 AS app WORKDIR /app COPY --from=app-publish /app/src/Exceptionless.Web/out ./ @@ -145,7 +145,7 @@ USER elasticsearch RUN wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh && \ chmod +x dotnet-install.sh && \ - ./dotnet-install.sh --version 10.0.13 --runtime aspnetcore && \ + ./dotnet-install.sh --version 11.0.0-rc.2.26504.105 --runtime aspnetcore && \ rm dotnet-install.sh EXPOSE 8080 9200 diff --git a/README.md b/README.md index 46dd2eedd8..e53da947ee 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ _In appreciation for anyone who submits a non-trivial pull request, we will give Start here: 1. Read the [contributing document](https://github.com/exceptionless/Exceptionless/blob/main/CONTRIBUTING.md). -2. Install [Docker](https://www.docker.com/get-docker), [.NET 10.0](https://dotnet.microsoft.com/), and [Node 24+](https://nodejs.org/). +2. Install [Docker](https://www.docker.com/get-docker), [.NET 11.0](https://dotnet.microsoft.com/), and [Node 24+](https://nodejs.org/). 3. Run the app with one of these entry points: - Visual Studio Code: open the repo root and start the `Aspire` launch configuration. - Visual Studio: open `Exceptionless.slnx`, set `Exceptionless.AppHost` as the startup project, and run it. diff --git a/build/EmailTemplatePreviews.cs b/build/EmailTemplatePreviews.cs index cc0d07f70e..1e88ed5481 100644 --- a/build/EmailTemplatePreviews.cs +++ b/build/EmailTemplatePreviews.cs @@ -1,5 +1,5 @@ #:sdk Microsoft.NET.Sdk -#:property TargetFramework=net10.0 +#:property TargetFramework=net11.0 #:project ../src/Exceptionless.EmailTemplates/Exceptionless.EmailTemplates.csproj // Run from the repository root. diff --git a/global.json b/global.json index ab000ad016..7e2514b3e4 100644 --- a/global.json +++ b/global.json @@ -1,8 +1,8 @@ { "sdk": { - "version": "10.0.100", + "version": "11.0.100-rc.2.26504.105", "rollForward": "latestMinor", - "allowPrerelease": false + "allowPrerelease": true }, "test": { "runner": "Microsoft.Testing.Platform" diff --git a/src/Directory.Build.props b/src/Directory.Build.props index fa0f2d35de..dad7036eec 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,6 +1,6 @@ - net10.0 + net11.0 enable Exceptionless true diff --git a/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj b/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj index 335f84a5b3..c54dcd60df 100644 --- a/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj +++ b/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj @@ -1,7 +1,7 @@ Exe - net10.0 + net11.0 enable enable a9c2ddcc-e51d-4cd1-9782-96e1d74eec87 diff --git a/src/Exceptionless.Core/Exceptionless.Core.csproj b/src/Exceptionless.Core/Exceptionless.Core.csproj index 80adfbd569..c62aa7bd5c 100644 --- a/src/Exceptionless.Core/Exceptionless.Core.csproj +++ b/src/Exceptionless.Core/Exceptionless.Core.csproj @@ -7,14 +7,14 @@ - + - - - + + + - + diff --git a/src/Exceptionless.Insulation/Exceptionless.Insulation.csproj b/src/Exceptionless.Insulation/Exceptionless.Insulation.csproj index e63e29442f..bbb1b7bd0a 100644 --- a/src/Exceptionless.Insulation/Exceptionless.Insulation.csproj +++ b/src/Exceptionless.Insulation/Exceptionless.Insulation.csproj @@ -7,12 +7,11 @@ - - - - - - + + + + + @@ -20,7 +19,7 @@ - + diff --git a/src/Exceptionless.Web/Exceptionless.Web.csproj b/src/Exceptionless.Web/Exceptionless.Web.csproj index d33bbbd782..c72d585d3f 100644 --- a/src/Exceptionless.Web/Exceptionless.Web.csproj +++ b/src/Exceptionless.Web/Exceptionless.Web.csproj @@ -15,10 +15,8 @@ - - - + + diff --git a/src/Exceptionless.Web/Utility/OpenApi/EndpointDocumentationOperationTransformer.cs b/src/Exceptionless.Web/Utility/OpenApi/EndpointDocumentationOperationTransformer.cs index 169bb5f1d7..be09d9f00f 100644 --- a/src/Exceptionless.Web/Utility/OpenApi/EndpointDocumentationOperationTransformer.cs +++ b/src/Exceptionless.Web/Utility/OpenApi/EndpointDocumentationOperationTransformer.cs @@ -158,7 +158,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform requestBody.Required = true; if (requestBody.Content is not null) { - foreach (var mediaType in requestBody.Content.Values) + foreach (var mediaType in requestBody.Content.Values.OfType()) mediaType.Schema = RemoveNullAlternative(mediaType.Schema); } } diff --git a/src/Exceptionless.Web/Utility/OpenApi/ExceptionlessOpenApiServiceCollectionExtensions.cs b/src/Exceptionless.Web/Utility/OpenApi/ExceptionlessOpenApiServiceCollectionExtensions.cs index 9fe098e181..f46c76722a 100644 --- a/src/Exceptionless.Web/Utility/OpenApi/ExceptionlessOpenApiServiceCollectionExtensions.cs +++ b/src/Exceptionless.Web/Utility/OpenApi/ExceptionlessOpenApiServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi; namespace Exceptionless.Web.Utility.OpenApi; @@ -10,6 +11,8 @@ public static IServiceCollection AddExceptionlessOpenApi(this IServiceCollection { services.AddOpenApi(options => { + // ASP.NET Core 11 defaults to OpenAPI 3.2; keep publishing 3.1 for existing client generators. + options.OpenApiVersion = OpenApiSpecVersion.OpenApi3_1; options.CreateSchemaReferenceId = SchemaReferenceIdHelper.CreateSchemaReferenceId; options.AddDocumentTransformer(); options.AddDocumentTransformer(); diff --git a/src/Exceptionless.Web/Utility/OpenApi/RequestBodyContentOperationTransformer.cs b/src/Exceptionless.Web/Utility/OpenApi/RequestBodyContentOperationTransformer.cs index f5dec66607..387cabf9f5 100644 --- a/src/Exceptionless.Web/Utility/OpenApi/RequestBodyContentOperationTransformer.cs +++ b/src/Exceptionless.Web/Utility/OpenApi/RequestBodyContentOperationTransformer.cs @@ -32,9 +32,9 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform operation.RequestBody = new OpenApiRequestBody { Required = true, - Content = new Dictionary + Content = new Dictionary { - ["multipart/form-data"] = new() + ["multipart/form-data"] = new OpenApiMediaType { Schema = CreateMultipartSchema(multipartFileUploadAttribute) } @@ -61,7 +61,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform operation.RequestBody = new OpenApiRequestBody { Required = true, - Content = new Dictionary() + Content = new Dictionary() }; foreach (string contentType in contentTypes) diff --git a/tests/Exceptionless.Tests/Api/Data/openapi.json b/tests/Exceptionless.Tests/Api/Data/openapi.json index 17f157531e..89e7f3fe66 100644 --- a/tests/Exceptionless.Tests/Api/Data/openapi.json +++ b/tests/Exceptionless.Tests/Api/Data/openapi.json @@ -1,5 +1,5 @@ { - "openapi": "3.1.1", + "openapi": "3.1.2", "info": { "title": "Exceptionless API", "termsOfService": "https://exceptionless.com/terms/", @@ -16,7 +16,7 @@ }, "servers": [ { - "url": "http://localhost/" + "url": "http://localhost" } ], "paths": { @@ -1402,9 +1402,6 @@ "202": { "description": "Accepted" }, - "413": { - "description": "Payload Too Large" - }, "400": { "description": "No project id specified and no default project was found.", "content": { @@ -1424,6 +1421,9 @@ } } } + }, + "413": { + "description": "Payload Too Large" } }, "deprecated": true @@ -1478,9 +1478,6 @@ "202": { "description": "Accepted" }, - "413": { - "description": "Payload Too Large" - }, "400": { "description": "No project id specified and no default project was found.", "content": { @@ -1500,6 +1497,9 @@ } } } + }, + "413": { + "description": "Payload Too Large" } }, "deprecated": true @@ -3467,8 +3467,8 @@ } } }, - "409": { - "description": "The saved view preferences could not be updated due to a concurrent request.", + "404": { + "description": "The organization could not be found.", "content": { "application/problem\u002Bjson": { "schema": { @@ -3477,8 +3477,8 @@ } } }, - "404": { - "description": "The organization could not be found.", + "409": { + "description": "The saved view preferences could not be updated due to a concurrent request.", "content": { "application/problem\u002Bjson": { "schema": { @@ -3564,8 +3564,8 @@ } } }, - "409": { - "description": "The saved view preferences could not be updated due to a concurrent request.", + "404": { + "description": "The organization could not be found.", "content": { "application/problem\u002Bjson": { "schema": { @@ -3574,8 +3574,8 @@ } } }, - "404": { - "description": "The organization could not be found.", + "409": { + "description": "The saved view preferences could not be updated due to a concurrent request.", "content": { "application/problem\u002Bjson": { "schema": { @@ -4101,8 +4101,8 @@ } } }, - "422": { - "description": "The product tour name is invalid.", + "404": { + "description": "The current user could not be found.", "content": { "application/problem\u002Bjson": { "schema": { @@ -4111,8 +4111,8 @@ } } }, - "404": { - "description": "The current user could not be found.", + "422": { + "description": "The product tour name is invalid.", "content": { "application/problem\u002Bjson": { "schema": { @@ -7767,22 +7767,22 @@ "200": { "description": "OK" }, - "422": { - "description": "The requested status is invalid.", + "404": { + "description": "One or more stacks could not be found.", "content": { "application/problem\u002Bjson": { "schema": { - "$ref": "#/components/schemas/HttpValidationProblemDetails" + "$ref": "#/components/schemas/ProblemDetails" } } } }, - "404": { - "description": "One or more stacks could not be found.", + "422": { + "description": "The requested status is invalid.", "content": { "application/problem\u002Bjson": { "schema": { - "$ref": "#/components/schemas/ProblemDetails" + "$ref": "#/components/schemas/HttpValidationProblemDetails" } } } @@ -9093,9 +9093,6 @@ "202": { "description": "Accepted" }, - "413": { - "description": "Payload Too Large" - }, "400": { "description": "No project id specified and no default project was found.", "content": { @@ -9115,6 +9112,9 @@ } } } + }, + "413": { + "description": "Payload Too Large" } } } @@ -9465,9 +9465,6 @@ "202": { "description": "Accepted" }, - "413": { - "description": "Payload Too Large" - }, "400": { "description": "No project id specified and no default project was found.", "content": { @@ -9487,6 +9484,9 @@ } } } + }, + "413": { + "description": "Payload Too Large" } } } @@ -15161,43 +15161,43 @@ }, "tags": [ { - "name": "AssistantEndpoints" - }, - { - "name": "Project" + "name": "AdminEndpoints" }, { - "name": "AdminEndpoints" + "name": "AssistantEndpoints" }, { - "name": "OAuth" + "name": "Auth" }, { "name": "Event" }, { - "name": "Auth" + "name": "OAuth" }, { - "name": "Token" + "name": "Organization" }, { - "name": "WebHook" + "name": "Project" }, { "name": "SavedView" }, { - "name": "User" + "name": "Source Map" }, { - "name": "Organization" + "name": "Stack" }, { - "name": "Stack" + "name": "Token" }, { - "name": "Source Map" + "name": "User" + }, + { + "name": "WebHook" } ] } diff --git a/tests/Exceptionless.Tests/Exceptionless.Tests.csproj b/tests/Exceptionless.Tests/Exceptionless.Tests.csproj index d077bbf8ce..2c4d130a60 100644 --- a/tests/Exceptionless.Tests/Exceptionless.Tests.csproj +++ b/tests/Exceptionless.Tests/Exceptionless.Tests.csproj @@ -4,6 +4,9 @@ False false true + + false $(InterceptorsNamespaces);Microsoft.AspNetCore.OpenApi.Generated @@ -13,10 +16,10 @@ - - + + - + diff --git a/tests/Exceptionless.Tests/Jobs/CloseInactiveSessionsJobTests.cs b/tests/Exceptionless.Tests/Jobs/CloseInactiveSessionsJobTests.cs index ab17d7f345..ba2f586e20 100644 --- a/tests/Exceptionless.Tests/Jobs/CloseInactiveSessionsJobTests.cs +++ b/tests/Exceptionless.Tests/Jobs/CloseInactiveSessionsJobTests.cs @@ -164,15 +164,17 @@ public async Task CloseInactiveSessions(int defaultInactivePeriodInMinutes, bool Assert.Equal(2, events.Total); sessionStart = events.Documents.First(e => e.IsSessionStart()); - decimal sessionStartDuration = (decimal)(sessionHeartbeatUpdatedAgoInSeconds.HasValue ? (utcNow.SubtractSeconds(sessionHeartbeatUpdatedAgoInSeconds.Value) - sessionStart.Date.UtcDateTime).TotalSeconds : 0); + // Event values are persisted as doubles, so compare at that precision. + double sessionStartDuration = sessionHeartbeatUpdatedAgoInSeconds.HasValue ? (utcNow.SubtractSeconds(sessionHeartbeatUpdatedAgoInSeconds.Value) - sessionStart.Date.UtcDateTime).TotalSeconds : 0; + double? sessionStartValue = (double?)sessionStart.Value; if (willCloseSession) { - Assert.Equal(sessionStartDuration, sessionStart.Value); + Assert.Equal(sessionStartDuration, sessionStartValue); Assert.True(sessionStart.HasSessionEndTime()); } else { - Assert.Equal(sessionStartDuration, sessionStart.Value); + Assert.Equal(sessionStartDuration, sessionStartValue); Assert.False(sessionStart.HasSessionEndTime()); } } diff --git a/tests/ci/backend-shards.mjs b/tests/ci/backend-shards.mjs index fb2406616d..a6b4dddeb4 100644 --- a/tests/ci/backend-shards.mjs +++ b/tests/ci/backend-shards.mjs @@ -53,7 +53,7 @@ export function summarize(results) { } function run(args) { - const assembly = resolve(args.assembly ?? join(root, 'tests/Exceptionless.Tests/bin/Release/net10.0/Exceptionless.Tests.dll')); + const assembly = resolve(args.assembly ?? join(root, 'tests/Exceptionless.Tests/bin/Release/net11.0/Exceptionless.Tests.dll')); const discovered = JSON.parse( execFileSync('dotnet', [assembly, '--list-tests', 'json', '--no-ansi'], { encoding: 'utf8',