From 7742053fbf19833016aef1970caaabe8581a40f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81api=C5=84ski?= Date: Fri, 16 Oct 2026 20:37:42 +0200 Subject: [PATCH 1/5] fix(deps): reference the patched System.Security.Cryptography.Pkcs Microsoft.Data.SqlClient depends on Pkcs versions with known vulnerabilities (CVE-2026-84008, CVE-2026-70162); ConfigWay.SqlServer references 8.0.2, 9.0.21 and 10.0.13 per target. --- src/ConfigWay.SqlServer/ConfigWay.SqlServer.csproj | 2 ++ src/Directory.Packages.props | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/ConfigWay.SqlServer/ConfigWay.SqlServer.csproj b/src/ConfigWay.SqlServer/ConfigWay.SqlServer.csproj index e8cb3bb..fa99442 100644 --- a/src/ConfigWay.SqlServer/ConfigWay.SqlServer.csproj +++ b/src/ConfigWay.SqlServer/ConfigWay.SqlServer.csproj @@ -7,6 +7,8 @@ + + diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 2e6ce0e..83fafce 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -18,6 +18,16 @@ 9.0.20 + + + 10.0.13 + 8.0.2 + 9.0.21 + + @@ -33,6 +43,7 @@ + From feb1f5f5ff475893e2dae095a51b06ac49c2adf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81api=C5=84ski?= Date: Fri, 16 Oct 2026 20:37:43 +0200 Subject: [PATCH 2/5] chore: update Reiho to 2.0.0 Requests to the editor's API are strict: unknown fields and missing non-nullable values return 400. --- src/Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 83fafce..d951d16 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -46,7 +46,7 @@ - + From 7701be4fce1e095e2e1133119b14c6745cfc4c51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81api=C5=84ski?= Date: Fri, 16 Oct 2026 20:37:43 +0200 Subject: [PATCH 3/5] feat!: answer UpdateConfiguration errors with problem details UpdateConfiguration returns 204 on success; when settings do not pass validation it answers 400 with the messages under settings, and nothing is saved. The editor reads the messages from the response. --- .../Integration/PostgresApiTests.cs | 7 +- .../Integration/SqlServerApiTests.cs | 7 +- .../Integration/ConfigurationApiTests.cs | 39 ++++++++--- .../Unit/UpdateConfigurationHandlerTests.cs | 67 ++++++++++++------- .../UpdateConfiguration.cs | 2 +- .../UpdateConfigurationHandler.cs | 12 ++-- src/ConfigWay.UI/SPA/src/api/api.ts | 38 ++++++++++- .../SPA/src/pages/AllSettingsPage.tsx | 14 ++-- .../SPA/src/pages/SectionPage.tsx | 14 ++-- 9 files changed, 129 insertions(+), 71 deletions(-) diff --git a/src/ConfigWay.PostgreSQL.Tests/Integration/PostgresApiTests.cs b/src/ConfigWay.PostgreSQL.Tests/Integration/PostgresApiTests.cs index 359d69f..8802f17 100644 --- a/src/ConfigWay.PostgreSQL.Tests/Integration/PostgresApiTests.cs +++ b/src/ConfigWay.PostgreSQL.Tests/Integration/PostgresApiTests.cs @@ -71,14 +71,11 @@ public async Task GetConfiguration_InitialValues_AreNull() } [Fact] - public async Task UpdateConfiguration_ValidSettings_Returns200WithEmptyErrors() + public async Task UpdateConfiguration_ValidSettings_Returns204() { var response = await PostUpdate(new[] { new { key = "Simple:Name", value = "Alice" } }); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var errors = await response.Content.ReadFromJsonAsync(); - Assert.NotNull(errors); - Assert.Empty(errors); + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); } [Fact] diff --git a/src/ConfigWay.SqlServer.Tests/Integration/SqlServerApiTests.cs b/src/ConfigWay.SqlServer.Tests/Integration/SqlServerApiTests.cs index 5f46efb..0d3c41a 100644 --- a/src/ConfigWay.SqlServer.Tests/Integration/SqlServerApiTests.cs +++ b/src/ConfigWay.SqlServer.Tests/Integration/SqlServerApiTests.cs @@ -71,14 +71,11 @@ public async Task GetConfiguration_InitialValues_AreNull() } [Fact] - public async Task UpdateConfiguration_ValidSettings_Returns200WithEmptyErrors() + public async Task UpdateConfiguration_ValidSettings_Returns204() { var response = await PostUpdate(new[] { new { key = "Simple:Name", value = "Alice" } }); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var errors = await response.Content.ReadFromJsonAsync(); - Assert.NotNull(errors); - Assert.Empty(errors); + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); } [Fact] diff --git a/src/ConfigWay.Tests/Integration/ConfigurationApiTests.cs b/src/ConfigWay.Tests/Integration/ConfigurationApiTests.cs index d96edfb..347f62d 100644 --- a/src/ConfigWay.Tests/Integration/ConfigurationApiTests.cs +++ b/src/ConfigWay.Tests/Integration/ConfigurationApiTests.cs @@ -97,15 +97,37 @@ public async Task GetConfiguration_InitialValues_AreNull() } [Fact] - public async Task UpdateConfiguration_ValidSettings_Returns200WithEmptyErrors() + public async Task UpdateConfiguration_ValidSettings_Returns204() { var settings = new[] { new Setting("Simple:Name", "Alice") }; var response = await PostUpdateConfiguration(settings); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var errors = await response.Content.ReadFromJsonAsync(); - Assert.NotNull(errors); - Assert.Empty(errors); + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + } + + [Fact] + public async Task UpdateConfiguration_InvalidValue_ReturnsValidationProblem() + { + var response = await PostUpdateConfiguration([new Setting("Typed:Count", "not a number")]); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + Assert.Equal("application/problem+json", response.Content.Headers.ContentType?.MediaType); + var problem = await response.Content.ReadFromJsonAsync(JsonOpts); + var message = Assert.Single(problem.GetProperty("errors").GetProperty("settings").EnumerateArray()); + Assert.StartsWith("Typed:Count", message.GetString()); + } + + [Fact] + public async Task UpdateConfiguration_InvalidValue_NothingSaved() + { + await PostUpdateConfiguration([new Setting("Simple:Name", "Alice"), new Setting("Typed:Count", "not a number")]); + + var sections = await FetchSections(); + var field = sections + .Single(s => s.Key == "Simple") + .Fields.Single(f => f.Key == "Name"); + + Assert.Null(field.Value); } [Fact] @@ -385,16 +407,13 @@ await PostUpdateConfiguration([ } [Fact] - public async Task UpdateConfiguration_DeleteReturns200WithEmptyErrors() + public async Task UpdateConfiguration_Delete_Returns204() { await PostUpdateConfiguration([new Setting("Arrays:Tags:0", "temp")]); var response = await PostUpdateConfiguration([], keysToDelete: ["Arrays:Tags:0"]); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var errors = await response.Content.ReadFromJsonAsync(); - Assert.NotNull(errors); - Assert.Empty(errors); + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); } private Task PostGetConfiguration() => diff --git a/src/ConfigWay.Tests/Unit/UpdateConfigurationHandlerTests.cs b/src/ConfigWay.Tests/Unit/UpdateConfigurationHandlerTests.cs index 8de3824..a3e2bd4 100644 --- a/src/ConfigWay.Tests/Unit/UpdateConfigurationHandlerTests.cs +++ b/src/ConfigWay.Tests/Unit/UpdateConfigurationHandlerTests.cs @@ -2,6 +2,8 @@ using Kododo.ConfigWay.Core.Model; using Kododo.ConfigWay.Core.Store; using Kododo.ConfigWay.UI.API.UpdateConfiguration; +using Kododo.Reiho.AspNetCore.API; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -24,13 +26,28 @@ public UpdateConfigurationHandlerTests() BuildServiceProvider(); } + /// Runs the handler: no messages when it succeeds, else those of the validation problem it throws. + private static async Task ErrorsOf(Task handling) + { + try + { + await handling; + return []; + } + catch (RequestException e) + { + Assert.Equal(StatusCodes.Status400BadRequest, e.StatusCode); + return Assert.Single(e.Errors!, x => x.Key == "settings").Value; + } + } + [Fact] public async Task HandleAsync_ValidSettings_ReturnsEmptyErrors() { var handler = CreateHandler(new CoreOptions("Simple", typeof(SimpleOptions))); var settings = new[] { new Setting("Simple:Name", "Alice") }; - var errors = await handler.HandleAsync(new UpdateConfiguration(settings), default); + var errors = await ErrorsOf(handler.HandleAsync(new UpdateConfiguration(settings), default)); Assert.Empty(errors); } @@ -41,7 +58,7 @@ public async Task HandleAsync_ValidSettings_PersistsToStore() var handler = CreateHandler(new CoreOptions("Simple", typeof(SimpleOptions))); var settings = new[] { new Setting("Simple:Name", "Alice") }; - await handler.HandleAsync(new UpdateConfiguration(settings), default); + await ErrorsOf(handler.HandleAsync(new UpdateConfiguration(settings), default)); await _store.Received(1).SetAsync( Arg.Is>(s => s.Any(x => x.Key == "Simple:Name")), @@ -54,7 +71,7 @@ public async Task HandleAsync_ValidSettings_TriggersReload() var handler = CreateHandler(new CoreOptions("Simple", typeof(SimpleOptions))); var settings = new[] { new Setting("Simple:Name", "Alice") }; - await handler.HandleAsync(new UpdateConfiguration(settings), default); + await ErrorsOf(handler.HandleAsync(new UpdateConfiguration(settings), default)); await _editor.Received(1).ReloadAllAsync(Arg.Any()); } @@ -65,7 +82,7 @@ public async Task HandleAsync_NullValue_Accepted() var handler = CreateHandler(new CoreOptions("Simple", typeof(SimpleOptions))); var settings = new[] { new Setting("Simple:Name", null) }; - var errors = await handler.HandleAsync(new UpdateConfiguration(settings), default); + var errors = await ErrorsOf(handler.HandleAsync(new UpdateConfiguration(settings), default)); Assert.Empty(errors); } @@ -77,7 +94,7 @@ public async Task HandleAsync_WhenValidatorFails_ReturnsErrors() var handler = CreateHandler(new CoreOptions("Simple", typeof(SimpleOptions))); var settings = new[] { new Setting("Simple:Name", "X") }; - var errors = await handler.HandleAsync(new UpdateConfiguration(settings), default); + var errors = await ErrorsOf(handler.HandleAsync(new UpdateConfiguration(settings), default)); Assert.Single(errors, e => e == AlwaysFailValidator.ErrorMessage); } @@ -89,7 +106,7 @@ public async Task HandleAsync_WhenValidatorFails_DoesNotPersistToStore() var handler = CreateHandler(new CoreOptions("Simple", typeof(SimpleOptions))); var settings = new[] { new Setting("Simple:Name", "X") }; - await handler.HandleAsync(new UpdateConfiguration(settings), default); + await ErrorsOf(handler.HandleAsync(new UpdateConfiguration(settings), default)); await _store.DidNotReceive().SetAsync(Arg.Any>(), Arg.Any()); } @@ -101,7 +118,7 @@ public async Task HandleAsync_WhenValidatorFails_DoesNotTriggerReload() var handler = CreateHandler(new CoreOptions("Simple", typeof(SimpleOptions))); var settings = new[] { new Setting("Simple:Name", "X") }; - await handler.HandleAsync(new UpdateConfiguration(settings), default); + await ErrorsOf(handler.HandleAsync(new UpdateConfiguration(settings), default)); await _editor.DidNotReceive().ReloadAllAsync(Arg.Any()); } @@ -113,7 +130,7 @@ public async Task HandleAsync_ConditionalValidator_FailsWhenNameEmpty() var handler = CreateHandler(new CoreOptions("Simple", typeof(SimpleOptions))); var settings = new[] { new Setting("Simple:Name", "") }; - var errors = await handler.HandleAsync(new UpdateConfiguration(settings), default); + var errors = await ErrorsOf(handler.HandleAsync(new UpdateConfiguration(settings), default)); Assert.Single(errors, e => e == ConditionalValidator.ErrorMessage); } @@ -125,7 +142,7 @@ public async Task HandleAsync_ConditionalValidator_PassesWhenNameProvided() var handler = CreateHandler(new CoreOptions("Simple", typeof(SimpleOptions))); var settings = new[] { new Setting("Simple:Name", "Alice") }; - var errors = await handler.HandleAsync(new UpdateConfiguration(settings), default); + var errors = await ErrorsOf(handler.HandleAsync(new UpdateConfiguration(settings), default)); Assert.Empty(errors); } @@ -138,7 +155,7 @@ public async Task HandleAsync_SettingsForDifferentSection_DoesNotRunValidator() var settings = new[] { new Setting("Other:SomeKey", "value") }; - var errors = await handler.HandleAsync(new UpdateConfiguration(settings), default); + var errors = await ErrorsOf(handler.HandleAsync(new UpdateConfiguration(settings), default)); Assert.Empty(errors); } @@ -153,7 +170,7 @@ public async Task HandleAsync_BoolValue_Accepted(string value) var handler = CreateHandler(new CoreOptions("T", typeof(TypedOptions))); var settings = new[] { new Setting("T:Enabled", value) }; - var errors = await handler.HandleAsync(new UpdateConfiguration(settings), default); + var errors = await ErrorsOf(handler.HandleAsync(new UpdateConfiguration(settings), default)); Assert.Empty(errors); } @@ -164,7 +181,7 @@ public async Task HandleAsync_InvalidBoolValue_ReturnsError() var handler = CreateHandler(new CoreOptions("T", typeof(TypedOptions))); var settings = new[] { new Setting("T:Enabled", "yes") }; - var errors = await handler.HandleAsync(new UpdateConfiguration(settings), default); + var errors = await ErrorsOf(handler.HandleAsync(new UpdateConfiguration(settings), default)); Assert.Single(errors, e => e.Contains("T:Enabled")); } @@ -175,7 +192,7 @@ public async Task HandleAsync_IntValue_Accepted() var handler = CreateHandler(new CoreOptions("T", typeof(TypedOptions))); var settings = new[] { new Setting("T:Count", "42") }; - var errors = await handler.HandleAsync(new UpdateConfiguration(settings), default); + var errors = await ErrorsOf(handler.HandleAsync(new UpdateConfiguration(settings), default)); Assert.Empty(errors); } @@ -186,7 +203,7 @@ public async Task HandleAsync_InvalidIntValue_ReturnsError() var handler = CreateHandler(new CoreOptions("T", typeof(TypedOptions))); var settings = new[] { new Setting("T:Count", "not-a-number") }; - var errors = await handler.HandleAsync(new UpdateConfiguration(settings), default); + var errors = await ErrorsOf(handler.HandleAsync(new UpdateConfiguration(settings), default)); Assert.Single(errors, e => e.Contains("T:Count")); } @@ -202,7 +219,7 @@ public async Task HandleAsync_EnumValue_Accepted(string value) var handler = CreateHandler(new CoreOptions("T", typeof(TypedOptions))); var settings = new[] { new Setting("T:Level", value) }; - var errors = await handler.HandleAsync(new UpdateConfiguration(settings), default); + var errors = await ErrorsOf(handler.HandleAsync(new UpdateConfiguration(settings), default)); Assert.Empty(errors); } @@ -213,7 +230,7 @@ public async Task HandleAsync_InvalidEnumValue_ReturnsError() var handler = CreateHandler(new CoreOptions("T", typeof(TypedOptions))); var settings = new[] { new Setting("T:Level", "Critical") }; - var errors = await handler.HandleAsync(new UpdateConfiguration(settings), default); + var errors = await ErrorsOf(handler.HandleAsync(new UpdateConfiguration(settings), default)); Assert.Single(errors, e => e.Contains("T:Level")); } @@ -224,7 +241,7 @@ public async Task HandleAsync_NestedProperty_ValidatesCorrectly() var handler = CreateHandler(new CoreOptions("Root", typeof(NestedOptions))); var settings = new[] { new Setting("Root:Child:ChildProp", "value") }; - var errors = await handler.HandleAsync(new UpdateConfiguration(settings), default); + var errors = await ErrorsOf(handler.HandleAsync(new UpdateConfiguration(settings), default)); Assert.Empty(errors); } @@ -235,7 +252,7 @@ public async Task HandleAsync_UnknownProperty_ReturnsError() var handler = CreateHandler(new CoreOptions("Simple", typeof(SimpleOptions))); var settings = new[] { new Setting("Simple:DoesNotExist", "value") }; - var errors = await handler.HandleAsync(new UpdateConfiguration(settings), default); + var errors = await ErrorsOf(handler.HandleAsync(new UpdateConfiguration(settings), default)); Assert.Single(errors, e => e.Contains("Simple:DoesNotExist")); } @@ -246,7 +263,7 @@ public async Task HandleAsync_SimpleArrayItemKey_AcceptedWithoutError() var handler = CreateHandler(new CoreOptions("A", typeof(SimpleArrayOptions))); var settings = new[] { new Setting("A:Tags:0", "production") }; - var errors = await handler.HandleAsync(new UpdateConfiguration(settings), default); + var errors = await ErrorsOf(handler.HandleAsync(new UpdateConfiguration(settings), default)); Assert.Empty(errors); } @@ -257,7 +274,7 @@ public async Task HandleAsync_SimpleArrayItemKey_PersistedToStore() var handler = CreateHandler(new CoreOptions("A", typeof(SimpleArrayOptions))); var settings = new[] { new Setting("A:Tags:0", "production") }; - await handler.HandleAsync(new UpdateConfiguration(settings), default); + await ErrorsOf(handler.HandleAsync(new UpdateConfiguration(settings), default)); await _store.Received(1).SetAsync( Arg.Is>(s => s.Any(x => x.Key == "A:Tags:0")), @@ -270,7 +287,7 @@ public async Task HandleAsync_ComplexArrayItemFieldKey_AcceptedWithoutError() var handler = CreateHandler(new CoreOptions("C", typeof(ComplexArrayOptions))); var settings = new[] { new Setting("C:Items:0:Name", "First item") }; - var errors = await handler.HandleAsync(new UpdateConfiguration(settings), default); + var errors = await ErrorsOf(handler.HandleAsync(new UpdateConfiguration(settings), default)); Assert.Empty(errors); } @@ -286,7 +303,7 @@ public async Task HandleAsync_MultipleArrayItems_AllAccepted() new Setting("A:Tags:2", "gamma"), }; - var errors = await handler.HandleAsync(new UpdateConfiguration(settings), default); + var errors = await ErrorsOf(handler.HandleAsync(new UpdateConfiguration(settings), default)); Assert.Empty(errors); } @@ -300,7 +317,7 @@ public async Task HandleAsync_WithKeysToDelete_CallsDeleteOnStore() KeysToDelete = ["A:Tags:0", "A:Tags:1"], }; - await handler.HandleAsync(request, default); + await ErrorsOf(handler.HandleAsync(request, default)); await _store.Received(1).DeleteAsync( Arg.Is>(k => @@ -314,7 +331,7 @@ public async Task HandleAsync_WithKeysToDelete_TriggersReload() var handler = CreateHandler(new CoreOptions("A", typeof(SimpleArrayOptions))); var request = new UpdateConfiguration([]) { KeysToDelete = ["A:Tags:0"] }; - await handler.HandleAsync(request, default); + await ErrorsOf(handler.HandleAsync(request, default)); await _editor.Received(1).ReloadAllAsync(Arg.Any()); } @@ -325,7 +342,7 @@ public async Task HandleAsync_EmptyKeysToDelete_DoesNotCallDelete() var handler = CreateHandler(new CoreOptions("Simple", typeof(SimpleOptions))); var settings = new[] { new Setting("Simple:Name", "Alice") }; - await handler.HandleAsync(new UpdateConfiguration(settings), default); + await ErrorsOf(handler.HandleAsync(new UpdateConfiguration(settings), default)); await _store.DidNotReceive().DeleteAsync( Arg.Any>(), diff --git a/src/ConfigWay.UI/API/UpdateConfiguration/UpdateConfiguration.cs b/src/ConfigWay.UI/API/UpdateConfiguration/UpdateConfiguration.cs index 4760bb3..ec64389 100644 --- a/src/ConfigWay.UI/API/UpdateConfiguration/UpdateConfiguration.cs +++ b/src/ConfigWay.UI/API/UpdateConfiguration/UpdateConfiguration.cs @@ -3,7 +3,7 @@ namespace Kododo.ConfigWay.UI.API.UpdateConfiguration; -internal record UpdateConfiguration(Setting[] Settings) : IRequest +internal record UpdateConfiguration(Setting[] Settings) : IRequest { public string[] KeysToDelete { get; init; } = []; } \ No newline at end of file diff --git a/src/ConfigWay.UI/API/UpdateConfiguration/UpdateConfigurationHandler.cs b/src/ConfigWay.UI/API/UpdateConfiguration/UpdateConfigurationHandler.cs index 10bd459..fbdec7f 100644 --- a/src/ConfigWay.UI/API/UpdateConfiguration/UpdateConfigurationHandler.cs +++ b/src/ConfigWay.UI/API/UpdateConfiguration/UpdateConfigurationHandler.cs @@ -14,15 +14,20 @@ internal class UpdateConfigurationHandler( Configuration configuration, IConfigurationEditor configurationEditor, IConfiguration appConfiguration, - IServiceProvider services) : IRequestHandler + IServiceProvider services) : IRequestHandler { - public async Task HandleAsync( + public async Task HandleAsync( UpdateConfiguration request, CancellationToken stoppingToken) { + // The messages name the setting or the options section they are about, so they are all reported for the + // settings field rather than for single entries of it. var errors = Validate(request.Settings); if (errors.Length > 0) - return errors; + throw new RequestException("The settings are not valid.") + { + Errors = new Dictionary { ["settings"] = errors }, + }; var keysToDelete = request.KeysToDelete ?? []; if (keysToDelete.Length > 0) @@ -30,7 +35,6 @@ public async Task HandleAsync( await configuration.Store.SetAsync(request.Settings, stoppingToken); await configurationEditor.ReloadAllAsync(stoppingToken); - return []; } private string[] Validate(Setting[] incoming) diff --git a/src/ConfigWay.UI/SPA/src/api/api.ts b/src/ConfigWay.UI/SPA/src/api/api.ts index 33fa75b..b389895 100644 --- a/src/ConfigWay.UI/SPA/src/api/api.ts +++ b/src/ConfigWay.UI/SPA/src/api/api.ts @@ -2,6 +2,37 @@ import type { Section, Setting } from './api.model'; const BASE = `${import.meta.env.BASE_URL}api`; +/** + * The API turned the request down (400, 404, 409) with a problem details response; `messages` are its + * field errors, or its detail. + */ +export class ApiError extends Error { + readonly status: number; + readonly messages: string[]; + + constructor(status: number, messages: string[]) { + super(messages.join(' ')); + this.status = status; + this.messages = messages; + } +} + +interface ProblemDetails { + title?: string; + detail?: string; + errors?: Record; +} + +async function toApiError(res: Response): Promise { + if (!res.headers.get('Content-Type')?.includes('application/problem+json')) + return new Error(`${res.status} ${res.statusText}`); + const problem = (await res.json()) as ProblemDetails; + const messages = problem.errors + ? Object.values(problem.errors).flat() + : [problem.detail ?? problem.title ?? `${res.status} ${res.statusText}`]; + return new ApiError(res.status, messages); +} + async function post(path: string, body?: unknown): Promise { const res = await fetch(`${BASE}${path}`, { method: 'POST', @@ -9,7 +40,7 @@ async function post(path: string, body?: unknown): Promise { body: JSON.stringify(body ?? {}), }); - if (!res.ok) throw new Error(`${res.status} ${res.statusText}`); + if (!res.ok) throw await toApiError(res); const text = await res.text(); return (text ? JSON.parse(text) : null) as T; @@ -18,8 +49,9 @@ async function post(path: string, body?: unknown): Promise { export const fetchConfiguration = (): Promise => post('/GetConfiguration'); +/** Throws {@link ApiError} with the validation messages when the settings are not valid. */ export const saveSettings = ( settings: Setting[], keysToDelete: string[] = [], -): Promise => - post('/UpdateConfiguration', { settings, keysToDelete }); +): Promise => + post('/UpdateConfiguration', { settings, keysToDelete }); diff --git a/src/ConfigWay.UI/SPA/src/pages/AllSettingsPage.tsx b/src/ConfigWay.UI/SPA/src/pages/AllSettingsPage.tsx index 71d91a2..8b7597d 100644 --- a/src/ConfigWay.UI/SPA/src/pages/AllSettingsPage.tsx +++ b/src/ConfigWay.UI/SPA/src/pages/AllSettingsPage.tsx @@ -7,7 +7,7 @@ import SectionCard from '../components/settings/SectionCard'; import PageHeader from '../components/layout/PageHeader'; import { useI18n } from '../i18n/I18nContext'; import { useTheme } from '@mui/material/styles'; -import { saveSettings } from '../api/api'; +import { ApiError, saveSettings } from '../api/api'; import { collectFields, buildDraftFromSections, getChangedSettings, SENSITIVE_MASK, SENSITIVE_RESET } from '../utils/settings'; import type { ResetPatch } from '../utils/settings'; @@ -108,15 +108,11 @@ const AllSettingsPage = () => { setSaving(true); try { - const errors = await saveSettings(changed, keysToDelete); - if (errors.length > 0) { - setSaveErrors(errors); - } else { - setKeysToDelete([]); - await reload(); - } + await saveSettings(changed, keysToDelete); + setKeysToDelete([]); + await reload(); } catch (e) { - setSaveErrors([e instanceof Error ? e.message : t.unexpectedError]); + setSaveErrors(e instanceof ApiError ? e.messages : [e instanceof Error ? e.message : t.unexpectedError]); } finally { setSaving(false); } diff --git a/src/ConfigWay.UI/SPA/src/pages/SectionPage.tsx b/src/ConfigWay.UI/SPA/src/pages/SectionPage.tsx index 3b87ad3..161b1e5 100644 --- a/src/ConfigWay.UI/SPA/src/pages/SectionPage.tsx +++ b/src/ConfigWay.UI/SPA/src/pages/SectionPage.tsx @@ -6,7 +6,7 @@ import { useSections } from '../context/SectionsContext'; import SectionCard from '../components/settings/SectionCard'; import PageHeader from '../components/layout/PageHeader'; import { useTheme } from '@mui/material/styles'; -import { saveSettings } from '../api/api'; +import { ApiError, saveSettings } from '../api/api'; import { collectFields, buildDraft, getChangedSettings, SENSITIVE_MASK, SENSITIVE_RESET } from '../utils/settings'; import type { ResetPatch } from '../utils/settings'; import { useI18n } from '../i18n/I18nContext'; @@ -120,15 +120,11 @@ const SectionPage = () => { setSaving(true); try { - const errors = await saveSettings(changed, keysToDelete); - if (errors.length > 0) { - setSaveErrors(errors); - } else { - setKeysToDelete([]); - await reload(); - } + await saveSettings(changed, keysToDelete); + setKeysToDelete([]); + await reload(); } catch (e) { - setSaveErrors([e instanceof Error ? e.message : t.unexpectedError]); + setSaveErrors(e instanceof ApiError ? e.messages : [e instanceof Error ? e.message : t.unexpectedError]); } finally { setSaving(false); } From 916f9b297ce37c933da5aa842e034404312c7d4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81api=C5=84ski?= Date: Sat, 17 Oct 2026 13:20:37 +0200 Subject: [PATCH 4/5] feat!: run the editor in Way.Shell The editor is a Way.Shell module: layout, theme, language, API client and session handling come from the shell, and UseConfigWay() mounts it with MapWay. Outside Development the editor answers 403 until the host calls RequireAuthorization() or AllowAnonymous() on what UseConfigWay() returns. --- README.md | 23 +- src/ConfigWay.Demo.Web/Program.cs | 3 +- src/ConfigWay.Demo.Web/e2e/check.mjs | 100 +++ .../Integration/PostgresApiTests.cs | 4 +- .../Integration/SqlServerApiTests.cs | 4 +- .../Integration/ConfigurationApiTests.cs | 6 +- src/ConfigWay.Tests/Integration/ShellTests.cs | 69 ++ src/ConfigWay.UI/.nuget/README.md | 8 +- src/ConfigWay.UI/ConfigWay.UI.csproj | 10 +- .../ConfigurationBuilderExtensions.cs | 11 +- .../EndpointRouteBuilderExtensions.cs | 28 +- src/ConfigWay.UI/SPA/.npmrc | 3 + src/ConfigWay.UI/SPA/index.html | 7 +- src/ConfigWay.UI/SPA/package-lock.json | 751 ++++++++---------- src/ConfigWay.UI/SPA/package.json | 1 + src/ConfigWay.UI/SPA/src/App.tsx | 44 +- src/ConfigWay.UI/SPA/src/api/api.ts | 55 +- .../SPA/src/components/common/ToWayRoot.tsx | 11 + .../SPA/src/components/layout/MainLayout.tsx | 138 ---- .../SPA/src/components/layout/PageHeader.tsx | 46 +- .../SPA/src/components/layout/SideMenu.tsx | 122 +-- .../SPA/src/context/LayoutContext.tsx | 11 - .../SPA/src/context/SectionsContext.tsx | 6 +- .../SPA/src/context/ThemeContext.tsx | 119 --- src/ConfigWay.UI/SPA/src/i18n/I18nContext.tsx | 35 +- src/ConfigWay.UI/SPA/src/index.css | 24 - src/ConfigWay.UI/SPA/src/main.tsx | 13 +- .../SPA/src/pages/AllSettingsPage.tsx | 6 +- .../SPA/src/pages/SectionPage.tsx | 11 +- src/ConfigWay.UI/SPA/src/theme.ts | 61 ++ src/ConfigWay.UI/SPA/src/way.tsx | 6 + src/ConfigWay.UI/SPA/vite.config.ts | 13 +- src/Directory.Build.props | 9 + src/Directory.Packages.props | 5 +- 34 files changed, 803 insertions(+), 960 deletions(-) create mode 100644 src/ConfigWay.Demo.Web/e2e/check.mjs create mode 100644 src/ConfigWay.Tests/Integration/ShellTests.cs create mode 100644 src/ConfigWay.UI/SPA/.npmrc create mode 100644 src/ConfigWay.UI/SPA/src/components/common/ToWayRoot.tsx delete mode 100644 src/ConfigWay.UI/SPA/src/components/layout/MainLayout.tsx delete mode 100644 src/ConfigWay.UI/SPA/src/context/LayoutContext.tsx delete mode 100644 src/ConfigWay.UI/SPA/src/context/ThemeContext.tsx delete mode 100644 src/ConfigWay.UI/SPA/src/index.css create mode 100644 src/ConfigWay.UI/SPA/src/theme.ts create mode 100644 src/ConfigWay.UI/SPA/src/way.tsx diff --git a/README.md b/README.md index 0daa2fe..c9d3e91 100644 --- a/README.md +++ b/README.md @@ -196,11 +196,32 @@ Errors are shown in the UI and saving is blocked until they are resolved. ## Security -The `/config` route is not protected by default: +Outside the Development environment the editor and its API answer `403` until you decide who may use them: ```csharp app.UseConfigWay() .RequireAuthorization(policy => policy.RequireRole("Admin")); + +// or, for a public demo: +app.UseConfigWay().AllowAnonymous(); +``` + +A fallback authorization policy counts as a decision too. An editor nobody decided on is reported in the log at startup. + +## Way.Shell + +The editor runs in [Way.Shell](https://github.com/kododo-dev/WayShell), the UI shell shared by the Kododo Way libraries: the layout, theme, language and session handling come from the shell, and with other Ways in the same app it can also be opened from the shell's panel: + +```csharp +builder.Services.AddWayShell(o => +{ + o.Title = "Acme"; + o.HomeUrl = "~/"; + o.User = ctx => ctx.User.Identity?.IsAuthenticated == true ? new ShellUser(ctx.User.Identity.Name!) { SignOutUrl = "~/logout" } : null; +}); + +app.UseConfigWay("/config").RequireAuthorization("Admin"); +app.UseWayShell("/admin").RequireAuthorization(); // every Way the user may open ``` > Use `IOptionsSnapshot` or `IOptionsMonitor` in your code to always get the latest values after a reload. diff --git a/src/ConfigWay.Demo.Web/Program.cs b/src/ConfigWay.Demo.Web/Program.cs index ef53d7a..b1a7fda 100644 --- a/src/ConfigWay.Demo.Web/Program.cs +++ b/src/ConfigWay.Demo.Web/Program.cs @@ -43,6 +43,7 @@ return Results.Content(html, "text/html"); }); -app.UseConfigWay(); +// The public demo: anyone may edit its settings. +app.UseConfigWay().AllowAnonymous(); app.Run(); diff --git a/src/ConfigWay.Demo.Web/e2e/check.mjs b/src/ConfigWay.Demo.Web/e2e/check.mjs new file mode 100644 index 0000000..1843934 --- /dev/null +++ b/src/ConfigWay.Demo.Web/e2e/check.mjs @@ -0,0 +1,100 @@ +// Browser scenarios of the editor in Way.Shell, against the running demo (Production, AllowAnonymous): +// node check.mjs http://localhost:5089 [chrome|msedge|firefox|webkit] +// Needs the playwright package (npm i --no-save playwright); firefox and webkit need `npx playwright install`. +import { chromium, firefox, webkit } from 'playwright'; + +const base = process.argv[2].replace(/\/$/, ''); +const which = process.argv[3] ?? 'chrome'; +const browser = which === 'firefox' ? await firefox.launch() : which === 'webkit' ? await webkit.launch() : await chromium.launch({ channel: which }); +const results = []; +const record = (name, ok, detail = '') => results.push({ name, ok, detail }); +const visible = (l) => l.first().waitFor({ timeout: 10000 }).then(() => true, () => false); + +async function open(options = {}) { + const context = await browser.newContext({ locale: 'en-US', colorScheme: 'light', ...options }); + const page = await context.newPage(); + const errors = []; + page.on('pageerror', (e) => errors.push(e.message)); + page.on('console', (m) => { if (m.type() === 'error' && !m.text().startsWith('Failed to load resource')) errors.push(m.text()); }); + page.on('response', (r) => { if (r.status() >= 400 && !r.url().endsWith('/favicon.ico')) errors.push(`${r.status()} ${r.url()}`); }); + return { context, page, errors }; +} + +{ + const { context, page, errors } = await open(); + await page.goto(`${base}/config/`); + record('editor opens in the shell', await visible(page.getByRole('heading', { name: 'All Settings' }))); + record('title from the Way', (await page.title()) === 'Configuration', await page.title()); + const menu = page.getByTestId('way-menu'); + record('sections in the shell menu', await visible(menu.getByText('Branding'))); + + const font = await page.locator('main h1').first().evaluate((e) => getComputedStyle(e).fontFamily); // Typography h1, no font of its own + record("ConfigWay's monospace theme in its area", /IBM Plex Mono/.test(font), font); + const shellTitleFont = await page.locator('nav, [data-testid="way-menu"]').first().evaluate(() => + getComputedStyle(document.querySelector('.MuiDrawer-paper p')).fontFamily); + record("shell's own font in the shell", !/IBM Plex Mono/.test(shellTitleFont), shellTitleFont); + + await menu.getByText('Branding').click(); + record('section from the menu', (await visible(page.getByRole('heading', { name: 'Branding' }))) && page.url().endsWith('#/section/Branding'), page.url()); + await menu.getByText('Smtp').click(); + record('second section from the menu (paths from the Way root)', page.url().endsWith('#/section/Smtp'), page.url()); + await menu.getByText('Branding').click(); + await page.getByRole('heading', { name: 'Branding' }).waitFor(); + + // The fields are textareas; find Company Name by its value. + const fieldByValue = async (value) => { + await page.locator('main textarea').first().waitFor(); + const index = await page.locator('main textarea').evaluateAll((els, v) => els.findIndex((e) => e.value === v), value); + return index >= 0 ? page.locator('main textarea').nth(index) : null; + }; + const field = await fieldByValue('Acme Corp'); + record('field with its value', !!field); + if (field) { + await field.fill('Acme Pilot'); + await Promise.all([ + page.waitForResponse((r) => r.url().endsWith('/config/api/UpdateConfiguration')), + page.getByRole('button', { name: 'Save' }).click(), + ]); + await page.reload(); + const saved = await fieldByValue('Acme Pilot'); + record('saving through the shell API client', !!saved); + if (saved) { + await saved.fill('Acme Corp'); + await Promise.all([ + page.waitForResponse((r) => r.url().endsWith('/config/api/UpdateConfiguration')), + page.getByRole('button', { name: 'Save' }).click(), + ]); + } + } + + await page.goto(`${base}/config/#/section/Nope`); + await page.waitForTimeout(500); + record('unknown section goes to the Way root', page.url().endsWith('/config/#/'), page.url()); + + await page.getByRole('button', { name: 'Language' }).click(); + await page.getByRole('menuitem', { name: 'Polski' }).click(); + record('language from the shell', await visible(page.getByRole('heading', { name: 'Wszystkie ustawienia' }))); + + await page.getByRole('button', { name: 'Ciemny motyw' }).click(); + const header = await page.locator('main header').first().evaluate((e) => getComputedStyle(e).backgroundColor); + record("ConfigWay's dark paper in its area", header === 'rgb(36, 36, 36)', header); + record('no errors', errors.length === 0, errors.join(' | ')); + await context.close(); +} + +{ + const { context, page } = await open({ viewport: { width: 390, height: 800 } }); + await page.goto(`${base}/config/`); + await visible(page.getByRole('heading', { name: 'All Settings' })); + await page.getByRole('button', { name: 'Open menu' }).click(); + await page.getByTestId('way-menu').getByText('Smtp').click(); + const closed = await page.getByTestId('way-menu').waitFor({ state: 'hidden', timeout: 5000 }).then(() => true, () => false); + record('phone: a section from the menu closes it', closed && page.url().endsWith('#/section/Smtp'), page.url()); + await context.close(); +} + +await browser.close(); +const failed = results.filter((r) => !r.ok); +for (const r of results) console.log(`${r.ok ? 'ok ' : 'FAIL'} ${r.name}${r.detail ? ` — ${r.detail}` : ''}`); +console.log(`\n${which} ${base}: ${results.length - failed.length}/${results.length} ok`); +process.exit(failed.length ? 1 : 0); diff --git a/src/ConfigWay.PostgreSQL.Tests/Integration/PostgresApiTests.cs b/src/ConfigWay.PostgreSQL.Tests/Integration/PostgresApiTests.cs index 8802f17..008f6a4 100644 --- a/src/ConfigWay.PostgreSQL.Tests/Integration/PostgresApiTests.cs +++ b/src/ConfigWay.PostgreSQL.Tests/Integration/PostgresApiTests.cs @@ -34,7 +34,7 @@ public async Task InitializeAsync() }); _app = builder.Build(); - _app.UseConfigWay(); + _app.UseConfigWay().AllowAnonymous(); await _app.StartAsync(); _client = _app.GetTestClient(); @@ -188,7 +188,7 @@ private async Task CreateAppAsync() }); var app = builder.Build(); - app.UseConfigWay(); + app.UseConfigWay().AllowAnonymous(); await app.StartAsync(); return app; } diff --git a/src/ConfigWay.SqlServer.Tests/Integration/SqlServerApiTests.cs b/src/ConfigWay.SqlServer.Tests/Integration/SqlServerApiTests.cs index 0d3c41a..b55ca43 100644 --- a/src/ConfigWay.SqlServer.Tests/Integration/SqlServerApiTests.cs +++ b/src/ConfigWay.SqlServer.Tests/Integration/SqlServerApiTests.cs @@ -34,7 +34,7 @@ public async Task InitializeAsync() }); _app = builder.Build(); - _app.UseConfigWay(); + _app.UseConfigWay().AllowAnonymous(); await _app.StartAsync(); _client = _app.GetTestClient(); @@ -188,7 +188,7 @@ private async Task CreateAppAsync() }); var app = builder.Build(); - app.UseConfigWay(); + app.UseConfigWay().AllowAnonymous(); await app.StartAsync(); return app; } diff --git a/src/ConfigWay.Tests/Integration/ConfigurationApiTests.cs b/src/ConfigWay.Tests/Integration/ConfigurationApiTests.cs index 347f62d..afb906a 100644 --- a/src/ConfigWay.Tests/Integration/ConfigurationApiTests.cs +++ b/src/ConfigWay.Tests/Integration/ConfigurationApiTests.cs @@ -35,7 +35,7 @@ public async Task InitializeAsync() }); _app = builder.Build(); - _app.UseConfigWay(); + _app.UseConfigWay().AllowAnonymous(); await _app.StartAsync(); _client = _app.GetTestClient(); @@ -530,7 +530,7 @@ public async Task InitializeAsync() }); _app = builder.Build(); - _app.UseConfigWay(); + _app.UseConfigWay().AllowAnonymous(); await _app.StartAsync(); _client = _app.GetTestClient(); @@ -704,7 +704,7 @@ public async Task InitializeAsync() }); _app = builder.Build(); - _app.UseConfigWay(); + _app.UseConfigWay().AllowAnonymous(); await _app.StartAsync(); _client = _app.GetTestClient(); diff --git a/src/ConfigWay.Tests/Integration/ShellTests.cs b/src/ConfigWay.Tests/Integration/ShellTests.cs new file mode 100644 index 0000000..844760a --- /dev/null +++ b/src/ConfigWay.Tests/Integration/ShellTests.cs @@ -0,0 +1,69 @@ +using System.Net; +using Kododo.ConfigWay.UI; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.TestHost; +using Xunit; + +namespace Kododo.ConfigWay.Tests.Integration; + +/// The editor runs in Way.Shell: the shell's page, the editor's module, and the shell's protection. +public class ShellTests +{ + private static async Task<(WebApplication App, HttpClient Client)> StartAsync(Action? decide) + { + var builder = WebApplication.CreateBuilder(new WebApplicationOptions { EnvironmentName = "Production" }); + builder.WebHost.UseTestServer(); + builder.AddConfigWay(x => + { + x.AddOptions("Simple"); + x.AddUiEditor(); + }); + var app = builder.Build(); + var group = app.UseConfigWay("/settings"); + decide?.Invoke(group); + await app.StartAsync(); + return (app, app.GetTestClient()); + } + + [Fact] + public async Task Page_IsTheShellWithTheEditorsModule() + { + var (app, client) = await StartAsync(g => g.AllowAnonymous()); + await using var _ = app; + + var html = await client.GetStringAsync("/settings/"); + + Assert.Contains("""""", html); + Assert.Contains(""" - \ No newline at end of file + diff --git a/src/ConfigWay.UI/SPA/package-lock.json b/src/ConfigWay.UI/SPA/package-lock.json index b2ed766..e8cc045 100644 --- a/src/ConfigWay.UI/SPA/package-lock.json +++ b/src/ConfigWay.UI/SPA/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", + "@kododo-dev/way-shell": "file:../../../../WayShell/src/way-shell", "@mui/icons-material": "^9.0.0", "@mui/material": "^9.0.0", "react": "^19.2.4", @@ -32,12 +33,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -46,9 +47,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -56,21 +57,21 @@ } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -94,13 +95,13 @@ "license": "MIT" }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -110,14 +111,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -127,37 +128,37 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -167,27 +168,27 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -195,26 +196,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.10.tgz", + "integrity": "sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -233,31 +234,31 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.10.tgz", + "integrity": "sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.9", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", "debug": "^4.3.1" }, "engines": { @@ -265,52 +266,18 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@emnapi/core": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", - "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", - "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", - "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@emotion/babel-plugin": { "version": "11.13.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", @@ -615,29 +582,43 @@ } }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -712,6 +693,26 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kododo-dev/way-shell": { + "version": "0.0.0", + "resolved": "file:../../../../WayShell/src/way-shell", + "license": "MIT", + "peerDependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@mui/icons-material": "^9.0.0", + "@mui/material": "^9.0.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-router-dom": "^7.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, "node_modules/@mui/core-downloads-tracker": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-9.0.0.tgz", @@ -945,33 +946,14 @@ } } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz", - "integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, "node_modules/@oxc-project/types": { - "version": "0.123.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.123.0.tgz", - "integrity": "sha512-YtECP/y8Mj1lSHiUWGSRzy/C6teUKlS87dEfuVKT09LgQbUsBW1rNg+MiJ4buGu3yuADV60gbIvo9/HplA56Ew==", - "dev": true, + "version": "0.154.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.154.0.tgz", + "integrity": "sha512-KYOUGQFlLWR+s4QMV2Q9ko38TZm7EdO87Qm7rvZLqk/webse/cDjtyxKhg/47l0byCbs/k46LT8qc4PKjAikzA==", + "devOptional": true, "license": "MIT", "funding": { - "url": "https://github.com/sponsors/Boshen" + "url": "https://github.com/sponsors/oxc-project" } }, "node_modules/@popperjs/core": { @@ -984,14 +966,29 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.14.tgz", + "integrity": "sha512-aWR+XKH9+rW7m5fWq/5360J3mHycrx/o6xZM2DQFdJnTMdyP7/WzxPjhtLbVWdKo2y612/4U1bWqb3HQhiF7vg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.13.tgz", - "integrity": "sha512-5ZiiecKH2DXAVJTNN13gNMUcCDg4Jy8ZjbXEsPnqa248wgOVeYRX0iqXXD5Jz4bI9BFHgKsI2qmyJynstbmr+g==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.14.tgz", + "integrity": "sha512-QiVxHofl6A8lQU7WAtOUY4QMVZXohxwz7WJJoeh8NBEUyKfIkMRKhYyq8az/1RypNiIXzNTtiS6KwHz0erxYRQ==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1002,13 +999,12 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.13.tgz", - "integrity": "sha512-tz/v/8G77seu8zAB3A5sK3UFoOl06zcshEzhUO62sAEtrEuW/H1CcyoupOrD+NbQJytYgA4CppXPzlrmp4JZKA==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.14.tgz", + "integrity": "sha512-lTQOzHuBIooh3jIr5XINQkjPGEuGy+Dsy0meXDr6Sjty0b99kYgGh4uBC/8mJX1T0kC8d6S0aPP1Y+bHkh6aBw==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1019,13 +1015,12 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.13.tgz", - "integrity": "sha512-8DakphqOz8JrMYWTJmWA+vDJxut6LijZ8Xcdc4flOlAhU7PNVwo2MaWBF9iXjJAPo5rC/IxEFZDhJ3GC7NHvug==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.14.tgz", + "integrity": "sha512-hyMKEGatjmBYCs/Q4sbIcL/GqpFEqsX3wc+HYVQ/85A4rxc1o7rPm4DUI4GV4k/h0ITddwFSGpqQunI14GXjTA==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1036,13 +1031,12 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.13.tgz", - "integrity": "sha512-4wBQFfjDuXYN/SVI8inBF3Aa+isq40rc6VMFbk5jcpolUBTe5cYnMsHZ51nFWsx3PVyyNN3vgoESki0Hmr/4BA==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.14.tgz", + "integrity": "sha512-u371GbXoVXrEgP20PwzoGNOdAOqAMhzo19JzzY+V4YmmnC+DTOoqdbMu+O+54emM59Mik/UdrtWgcuEDKWRPKA==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1053,13 +1047,12 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.13.tgz", - "integrity": "sha512-JW/e4yPIXLms+jmnbwwy5LA/LxVwZUWLN8xug+V200wzaVi5TEGIWQlh8o91gWYFxW609euI98OCCemmWGuPrw==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.14.tgz", + "integrity": "sha512-BqwKsdZQf6EqVWr8fv2MrDYzAvOZ7+NYFSJESRGs87fkseEhs0TU9TXgRJTkZoRRkxTizyEg1LKUXfU3/78uqg==", "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1070,13 +1063,12 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.13.tgz", - "integrity": "sha512-ZfKWpXiUymDnavepCaM6KG/uGydJ4l2nBmMxg60Ci4CbeefpqjPWpfaZM7PThOhk2dssqBAcwLc6rAyr0uTdXg==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.14.tgz", + "integrity": "sha512-SD9kY8M009x/7s57Z1dgUVlP5+uYBgKQK0Tkr1WIWngrtUJmRCW1RJFMY7QDla2khXhmg2i5Io7MUjcmZNZ/KQ==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1087,13 +1079,12 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.13.tgz", - "integrity": "sha512-bmRg3O6Z0gq9yodKKWCIpnlH051sEfdVwt+6m5UDffAQMUUqU0xjnQqqAUm+Gu7ofAAly9DqiQDtKu2nPDEABA==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.14.tgz", + "integrity": "sha512-BJ2CL19M6uuWesMgnqJzgxoI+N+eV7b0A9O9vLV42y+gitC41fNa8KElau1tHVzuLWX5GzEuxMPhAO0tQXQMkg==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1104,13 +1095,12 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.13.tgz", - "integrity": "sha512-8Wtnbw4k7pMYN9B/mOEAsQ8HOiq7AZ31Ig4M9BKn2So4xRaFEhtCSa4ZJaOutOWq50zpgR4N5+L/opnlaCx8wQ==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.14.tgz", + "integrity": "sha512-4+Kl8daPV8NDHaD7mfCcc1pRM+Qg7ab8DyMm5FG5WoZgv4h2i3KFN1CybYBNbQqmuYm7ftJLvmMI1eHYJ22S/Q==", "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1121,13 +1111,12 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.13.tgz", - "integrity": "sha512-D/0Nlo8mQuxSMohNJUF2lDXWRsFDsHldfRRgD9bRgktj+EndGPj4DOV37LqDKPYS+osdyhZEH7fTakTAEcW7qg==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.14.tgz", + "integrity": "sha512-V/h8ePq1fKDrmtFMz6xcCN3DKvuvMRrwwD1q0PkBeeo4DpK64biaKvqWGVxqCeYKqeRk7gE7/iKPxLCn08O97A==", "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1138,13 +1127,12 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.13.tgz", - "integrity": "sha512-eRrPvat2YaVQcwwKi/JzOP6MKf1WRnOCr+VaI3cTWz3ZoLcP/654z90lVCJ4dAuMEpPdke0n+qyAqXDZdIC4rA==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.14.tgz", + "integrity": "sha512-WxalXzgrLVpKC93xfpaJ3gCXUWzeCr+FRCbDVD2mUAy4L6Vo3lNVz0epUEUpxPWJkLNYUwM4OSIikv36OJ9mLg==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1155,13 +1143,12 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.13.tgz", - "integrity": "sha512-PsdONiFRp8hR8KgVjTWjZ9s7uA3uueWL0t74/cKHfM4dR5zXYv4AjB8BvA+QDToqxAFg4ZkcVEqeu5F7inoz5w==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.14.tgz", + "integrity": "sha512-3G3tnAmTpdk9jO3GQtyHXqeFD2SlecfTws/Y3M2mmj9nMBwwuLT4kur8sZKeu7ENUHSpVO3cIsm5A52LEWWwlg==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1172,13 +1159,12 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.13.tgz", - "integrity": "sha512-hCNXgC5dI3TVOLrPT++PKFNZ+1EtS0mLQwfXXXSUD/+rGlB65gZDwN/IDuxLpQP4x8RYYHqGomlUXzpO8aVI2w==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.14.tgz", + "integrity": "sha512-qyr5XiNGxm2ytx17vvtcJ9nXn6v1gENETi503bJx3OGwZ1tSonj7BosxbMa/xeAfSl5iS9X6KjEWYXLu6v1L7Q==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1188,33 +1174,13 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.13.tgz", - "integrity": "sha512-viLS5C5et8NFtLWw9Sw3M/w4vvnVkbWkO7wSNh3C+7G1+uCkGpr6PcjNDSFcNtmXY/4trjPBqUfcOL+P3sWy/g==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.9.1", - "@emnapi/runtime": "1.9.1", - "@napi-rs/wasm-runtime": "^1.1.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.13.tgz", - "integrity": "sha512-Fqa3Tlt1xL4wzmAYxGNFV36Hb+VfPc9PYU+E25DAnswXv3ODDu/yyWjQDbXMo5AGWkQVjLgQExuVu8I/UaZhPQ==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.14.tgz", + "integrity": "sha512-oA4o5o0S2yB199YyYXDzKUv1Z56Rc+mgOjwm7Z/vJ9Z8igiDPYvoOuDyo6g7AK/ZaF09nPhDKVnGBcYHO11GXA==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1225,13 +1191,12 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.13.tgz", - "integrity": "sha512-/pLI5kPkGEi44TDlnbio3St/5gUFeN51YWNAk/Gnv6mEQBOahRBh52qVFVBpmrnU01n2yysvBML9Ynu7K4kGAQ==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.14.tgz", + "integrity": "sha512-9mE94bf2GZUaR/fUSXCMpBoWkctu421Mi0ORpm4zqfbkjKep7RxzPFa5etcGs5h+2KApoWBOA/hAJa/7saGgEQ==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1248,17 +1213,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1277,7 +1231,7 @@ "version": "24.12.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -1522,16 +1476,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { @@ -1730,9 +1684,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.16", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.16.tgz", - "integrity": "sha512-Lyf3aK28zpsD1yQMiiHD4RvVb6UdMoo8xzG2XzFIfR9luPzOpcBlAsT/qfB1XWS1bxWT+UtE4WmQgsp297FYOA==", + "version": "2.11.29", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.29.tgz", + "integrity": "sha512-YeesxhUmR4h5y5V8KF8iBCqJb8YFrBJ++XBTeGcWIaQOYXGN19JM0FoiOjAhwuDoln8owMgXcWBmgHtYSkky2w==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1743,9 +1697,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -1754,9 +1708,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "version": "4.29.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz", + "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==", "dev": true, "funding": [ { @@ -1774,11 +1728,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" + "baseline-browser-mapping": "^2.11.26", + "caniuse-lite": "^1.0.30001813", + "electron-to-chromium": "^1.5.439", + "node-releases": "^2.0.57", + "update-browserslist-db": "^1.3.3" }, "bin": { "browserslist": "cli.js" @@ -1797,9 +1751,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001787", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001787.tgz", - "integrity": "sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==", + "version": "1.0.30001816", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001816.tgz", + "integrity": "sha512-+8Yx7BXCoqk6IF8yTicjfUR6D/ldJzdlsodlEOiOSPmhK844WIgk67/zIQaGAOxNkZyyvBlrRFnLvqKIoqbAFw==", "dev": true, "funding": [ { @@ -1963,7 +1917,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -1980,9 +1934,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.334", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.334.tgz", - "integrity": "sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog==", + "version": "1.5.458", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.458.tgz", + "integrity": "sha512-/hIH2gRSmxrf4/X4Nw/t/Wc8PVS22l8/3HnWjWTWsTOM5dsnMUPZF/+u+yr3LqweNuTBJE1rq6qAQzKkZVwMfA==", "dev": true, "license": "ISC" }, @@ -2226,7 +2180,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -2301,7 +2255,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -2505,10 +2458,20 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -2594,10 +2557,10 @@ } }, "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "devOptional": true, "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -2610,27 +2573,26 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2645,13 +2607,12 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2666,13 +2627,12 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2687,13 +2647,12 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2708,13 +2667,12 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", "cpu": [ "arm" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2729,13 +2687,12 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2750,13 +2707,12 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2771,13 +2727,12 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2792,13 +2747,12 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2813,13 +2767,12 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2834,13 +2787,12 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2925,10 +2877,10 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "devOptional": true, "funding": [ { "type": "github", @@ -2951,11 +2903,14 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.37", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", - "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "version": "2.0.59", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.59.tgz", + "integrity": "sha512-5qr2okC4ItAI0hG5SaV0HoYMpnM/Y6+Oqr7AmPYy/2uEoGvS+GbSKLeRxSS1uAdu59yz0ouzcuXievSgg///WA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/object-assign": { "version": "4.1.1", @@ -3088,10 +3043,10 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=12" @@ -3101,10 +3056,10 @@ } }, "node_modules/postcss": { - "version": "8.5.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", - "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", - "dev": true, + "version": "8.5.30", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.30.tgz", + "integrity": "sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA==", + "devOptional": true, "funding": [ { "type": "opencollective", @@ -3121,9 +3076,9 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.20", "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "source-map-js": "^1.2.2" }, "engines": { "node": "^10 || ^12 || >=14" @@ -3194,9 +3149,9 @@ "license": "MIT" }, "node_modules/react-router": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.0.tgz", - "integrity": "sha512-m/xR9N4LQLmAS0ZhkY2nkPA1N7gQ5TUVa5n8TgANuDTARbn1gt+zLPXEm7W0XDTbrQ2AJSJKhoa6yx1D8BcpxQ==", + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.4.tgz", + "integrity": "sha512-PUPQcMhMGRAslLcvtlPz/kmzBEWPhLdgLFrL7pLNepBL6dX0lWj4WD2cUYVgYCuT3jxvghYFg81cDTj44DhetQ==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -3216,12 +3171,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.14.0.tgz", - "integrity": "sha512-2G3ajSVSZMEtmTjIklRWlNvo8wICEpLihfD/0YMDxbWK2UyP5EGfnoIn9AIQGnF3G/FX0MRbHXdFcD+rL1ZreQ==", + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.4.tgz", + "integrity": "sha512-yrfmJHIpDG7taCpqKjT1G5B6q3O2K+RN8/fgNf0lTjCwiPbQ0ei6vXX9ZjQR+7ld8Tr7Z5xmyMnZ8YJrphWQUw==", "license": "MIT", "dependencies": { - "react-router": "7.14.0" + "react-router": "7.18.4" }, "engines": { "node": ">=20.0.0" @@ -3277,14 +3232,14 @@ } }, "node_modules/rolldown": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.13.tgz", - "integrity": "sha512-bvVj8YJmf0rq4pSFmH7laLa6pYrhghv3PRzrCdRAr23g66zOKVJ4wkvFtgohtPLWmthgg8/rkaqRHrpUEh0Zbw==", - "dev": true, + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.14.tgz", + "integrity": "sha512-eTBTTqMR6uHKLgcUqpJP8ntzDI6V4LYA8negAMBEI7gkgVvOdrx6VsvS2Ym+83KhOPdLCHWJu0w7UQFJZhHKNw==", + "devOptional": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.123.0", - "@rolldown/pluginutils": "1.0.0-rc.13" + "@oxc-project/types": "=0.154.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" @@ -3293,28 +3248,28 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.13", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.13", - "@rolldown/binding-darwin-x64": "1.0.0-rc.13", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.13", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.13", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.13", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.13", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.13", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.13", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.13", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.13", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.13", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.13", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.13", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.13" + "@rolldown/binding-android-arm-eabi": "1.2.14", + "@rolldown/binding-android-arm64": "1.2.14", + "@rolldown/binding-darwin-arm64": "1.2.14", + "@rolldown/binding-darwin-x64": "1.2.14", + "@rolldown/binding-freebsd-x64": "1.2.14", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.14", + "@rolldown/binding-linux-arm64-gnu": "1.2.14", + "@rolldown/binding-linux-arm64-musl": "1.2.14", + "@rolldown/binding-linux-ppc64-gnu": "1.2.14", + "@rolldown/binding-linux-s390x-gnu": "1.2.14", + "@rolldown/binding-linux-x64-gnu": "1.2.14", + "@rolldown/binding-linux-x64-musl": "1.2.14", + "@rolldown/binding-openharmony-arm64": "1.2.14", + "@rolldown/binding-win32-arm64-msvc": "1.2.14", + "@rolldown/binding-win32-x64-msvc": "1.2.14" } }, "node_modules/rolldown/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.13.tgz", - "integrity": "sha512-3ngTAv6F/Py35BsYbeeLeecvhMKdsKm4AoOETVhAA+Qc8nrA2I0kF7oa93mE9qnIurngOSpMnQ0x2nQY2FPviA==", - "dev": true, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "devOptional": true, "license": "MIT" }, "node_modules/scheduler": { @@ -3372,10 +3327,10 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -3426,10 +3381,10 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", - "dev": true, + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "devOptional": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -3455,14 +3410,6 @@ "typescript": ">=4.8.4" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3518,13 +3465,13 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz", + "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==", "dev": true, "funding": [ { @@ -3563,17 +3510,17 @@ } }, "node_modules/vite": { - "version": "8.0.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.7.tgz", - "integrity": "sha512-P1PbweD+2/udplnThz3btF4cf6AgPky7kk23RtHUkJIU5BIxwPprhRGmOAHs6FTI7UiGbTNrgNP6jSYD6JaRnw==", - "dev": true, + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz", + "integrity": "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==", + "devOptional": true, "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.13", - "tinyglobby": "^0.2.15" + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.29", + "rolldown": "~1.2.12", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" @@ -3589,7 +3536,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", + "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", diff --git a/src/ConfigWay.UI/SPA/package.json b/src/ConfigWay.UI/SPA/package.json index f085146..d9557d0 100644 --- a/src/ConfigWay.UI/SPA/package.json +++ b/src/ConfigWay.UI/SPA/package.json @@ -12,6 +12,7 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", + "@kododo-dev/way-shell": "file:../../../../WayShell/src/way-shell", "@mui/icons-material": "^9.0.0", "@mui/material": "^9.0.0", "react": "^19.2.4", diff --git a/src/ConfigWay.UI/SPA/src/App.tsx b/src/ConfigWay.UI/SPA/src/App.tsx index e6681a7..87fc301 100644 --- a/src/ConfigWay.UI/SPA/src/App.tsx +++ b/src/ConfigWay.UI/SPA/src/App.tsx @@ -1,40 +1,22 @@ -import { HashRouter, Routes, Route, Navigate } from 'react-router-dom'; -import { ThemeProvider, CssBaseline } from '@mui/material'; -import { ThemeContextProvider, useThemeMode } from './context/ThemeContext'; -import { I18nProvider } from './i18n/I18nContext'; +import { Route, Routes } from 'react-router-dom'; import { SectionsProvider } from './context/SectionsContext'; -import MainLayout from './components/layout/MainLayout'; +import SideMenu from './components/layout/SideMenu'; +import ToWayRoot from './components/common/ToWayRoot'; import AllSettingsPage from './pages/AllSettingsPage'; import SectionPage from './pages/SectionPage'; -function AppInner() { - const { muiTheme } = useThemeMode(); - return ( - - - - - - - } /> - } /> - } /> - } /> - - - - - - ); -} - +// The editor inside Way.Shell: the shell provides the router, theme, language and layout; the sections go into the +// shell's side menu. function App() { return ( - - - - - + + + + } /> + } /> + } /> + + ); } diff --git a/src/ConfigWay.UI/SPA/src/api/api.ts b/src/ConfigWay.UI/SPA/src/api/api.ts index b389895..9ebf375 100644 --- a/src/ConfigWay.UI/SPA/src/api/api.ts +++ b/src/ConfigWay.UI/SPA/src/api/api.ts @@ -1,57 +1,16 @@ +import type { WayApi } from '@kododo-dev/way-shell'; import type { Section, Setting } from './api.model'; -const BASE = `${import.meta.env.BASE_URL}api`; +// Errors come from the shell's API client: ApiError (problem details), ForbiddenError, SessionExpiredError. +export { ApiError } from '@kododo-dev/way-shell'; -/** - * The API turned the request down (400, 404, 409) with a problem details response; `messages` are its - * field errors, or its detail. - */ -export class ApiError extends Error { - readonly status: number; - readonly messages: string[]; - - constructor(status: number, messages: string[]) { - super(messages.join(' ')); - this.status = status; - this.messages = messages; - } -} - -interface ProblemDetails { - title?: string; - detail?: string; - errors?: Record; -} - -async function toApiError(res: Response): Promise { - if (!res.headers.get('Content-Type')?.includes('application/problem+json')) - return new Error(`${res.status} ${res.statusText}`); - const problem = (await res.json()) as ProblemDetails; - const messages = problem.errors - ? Object.values(problem.errors).flat() - : [problem.detail ?? problem.title ?? `${res.status} ${res.statusText}`]; - return new ApiError(res.status, messages); -} - -async function post(path: string, body?: unknown): Promise { - const res = await fetch(`${BASE}${path}`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body ?? {}), - }); - - if (!res.ok) throw await toApiError(res); - - const text = await res.text(); - return (text ? JSON.parse(text) : null) as T; -} - -export const fetchConfiguration = (): Promise => - post('/GetConfiguration'); +export const fetchConfiguration = (api: WayApi): Promise => + api.post('GetConfiguration'); /** Throws {@link ApiError} with the validation messages when the settings are not valid. */ export const saveSettings = ( + api: WayApi, settings: Setting[], keysToDelete: string[] = [], ): Promise => - post('/UpdateConfiguration', { settings, keysToDelete }); + api.post('UpdateConfiguration', { settings, keysToDelete }); diff --git a/src/ConfigWay.UI/SPA/src/components/common/ToWayRoot.tsx b/src/ConfigWay.UI/SPA/src/components/common/ToWayRoot.tsx new file mode 100644 index 0000000..fd53235 --- /dev/null +++ b/src/ConfigWay.UI/SPA/src/components/common/ToWayRoot.tsx @@ -0,0 +1,11 @@ +import { useEffect } from 'react'; +import { useShell } from '@kododo-dev/way-shell'; + +/** Replaces the current address with the Way's root, e.g. for an unknown section. */ +const ToWayRoot = () => { + const { navigate, wayId } = useShell(); + useEffect(() => navigate(wayId, '', { replace: true }), [navigate, wayId]); + return null; +}; + +export default ToWayRoot; diff --git a/src/ConfigWay.UI/SPA/src/components/layout/MainLayout.tsx b/src/ConfigWay.UI/SPA/src/components/layout/MainLayout.tsx deleted file mode 100644 index c4fdc20..0000000 --- a/src/ConfigWay.UI/SPA/src/components/layout/MainLayout.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import React, { useState, useCallback } from 'react'; -import Drawer from '@mui/material/Drawer'; -import Box from '@mui/material/Box'; -import Typography from '@mui/material/Typography'; -import IconButton from '@mui/material/IconButton'; -import Tooltip from '@mui/material/Tooltip'; -import useMediaQuery from '@mui/material/useMediaQuery'; -import DarkModeIcon from '@mui/icons-material/DarkMode'; -import LightModeIcon from '@mui/icons-material/LightMode'; -import SideMenu from './SideMenu'; -import { useThemeMode } from '../../context/ThemeContext'; -import { useI18n } from '../../i18n/I18nContext'; -import { useTheme } from '@mui/material/styles'; -import { LayoutContext } from '../../context/LayoutContext'; - -const DRAWER_WIDTH = 248; - -const MainLayout = ({ children }: { children: React.ReactNode }) => { - const { mode, toggleMode } = useThemeMode(); - const { t } = useI18n(); - const theme = useTheme(); - const isDark = mode === 'dark'; - const isMobile = useMediaQuery(theme.breakpoints.down('md')); - - const [mobileOpen, setMobileOpen] = useState(false); - const openMobileMenu = useCallback(() => setMobileOpen(true), []); - const closeMobileMenu = useCallback(() => setMobileOpen(false), []); - - const drawerBg = isDark ? '#222' : '#fafafa'; - const borderColor = isDark ? '#2e2e2e' : '#e0e0e0'; - const bg = isDark ? '#1a1a1a' : '#f5f5f5'; - - const drawerContent = ( - <> - - - - {t.appTitle} - - - - - {isDark - ? - : - } - - - - - - ); - - return ( - - - {isMobile ? ( - - {drawerContent} - - ) : ( - - {drawerContent} - - )} - - - {children} - - - - ); -}; - -export default MainLayout; diff --git a/src/ConfigWay.UI/SPA/src/components/layout/PageHeader.tsx b/src/ConfigWay.UI/SPA/src/components/layout/PageHeader.tsx index cdb8f69..f9cdc9e 100644 --- a/src/ConfigWay.UI/SPA/src/components/layout/PageHeader.tsx +++ b/src/ConfigWay.UI/SPA/src/components/layout/PageHeader.tsx @@ -1,5 +1,4 @@ import Box from '@mui/material/Box'; -import Typography from '@mui/material/Typography'; import IconButton from '@mui/material/IconButton'; import InputBase from '@mui/material/InputBase'; import Tooltip from '@mui/material/Tooltip'; @@ -12,10 +11,8 @@ import SearchIcon from '@mui/icons-material/Search'; import CloseIcon from '@mui/icons-material/Close'; import type { SxProps } from '@mui/material'; import { useTheme } from '@mui/material/styles'; -import useMediaQuery from '@mui/material/useMediaQuery'; -import MenuIcon from '@mui/icons-material/Menu'; +import { PageHeader as ShellPageHeader } from '@kododo-dev/way-shell'; import { useI18n } from '../../i18n/I18nContext'; -import { useLayout } from '../../context/LayoutContext'; interface PageHeaderProps { title: string; @@ -41,12 +38,8 @@ const PageHeader = ({ }: PageHeaderProps) => { const theme = useTheme(); const { t } = useI18n(); - const { openMobileMenu } = useLayout(); - const isMobile = useMediaQuery(theme.breakpoints.down('md')); const isDark = theme.palette.mode === 'dark'; - const borderColor = isDark ? '#2e2e2e' : '#e0e0e0'; - const headerBg = isDark ? '#1e1e1e' : '#fff'; const searchBg = isDark ? '#2a2a2a' : '#f5f5f5'; const searchBorder = isDark ? '#363636' : '#ddd'; const searchFocusBorder = theme.palette.primary.main; @@ -56,38 +49,8 @@ const PageHeader = ({ return ( - - {isMobile && ( - - - - )} - - - {title} - - - + {/* The shell's header bar: the menu button on narrow screens, the title; ConfigWay's search and actions in it. */} + {title}}> {showSearch && ( )} - {!showSearch && } {showSave && hasChanges && onDiscard && (