From 4df9f9208c31ee04ee9d211fe73c4939c120fed9 Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Sun, 11 May 2025 15:29:00 +0200 Subject: [PATCH 01/18] Add account system --- app/lib/l10n/app_en.arb | 4 +- app/lib/main.dart | 24 ++------ app/lib/pages/settings/accounts.dart | 86 ++++++++++++++++++++++++++++ app/lib/pages/settings/home.dart | 21 ++++--- app/lib/services/file_system.dart | 40 +++++++++++++ 5 files changed, 149 insertions(+), 26 deletions(-) create mode 100644 app/lib/pages/settings/accounts.dart diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index 92cfb17d..a71bea40 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -249,5 +249,7 @@ "desktop": "Desktop", "compact": "Compact", "standard": "Standard", - "comfortable": "Comfortable" + "comfortable": "Comfortable", + "accounts": "Accounts", + "create": "Create" } diff --git a/app/lib/main.dart b/app/lib/main.dart index 89565b40..ccb2986f 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -10,9 +10,6 @@ import 'package:material_leap/material_leap.dart'; import 'package:setonix/pages/editor/shell.dart'; import 'package:setonix/pages/game/page.dart'; import 'package:setonix/pages/home/page.dart'; -import 'package:setonix/pages/settings/data.dart'; -import 'package:setonix/pages/settings/general.dart'; -import 'package:setonix/pages/settings/personalization.dart'; import 'package:setonix/services/file_system.dart'; import 'package:setonix/services/network.dart'; import 'package:setonix/theme.dart'; @@ -157,21 +154,12 @@ class SetonixApp extends StatelessWidget { GoRoute( path: 'settings', builder: (context, state) => const SettingsPage(), - routes: [ - GoRoute( - path: 'general', - builder: (context, state) => const GeneralSettingsPage(), - ), - GoRoute( - path: 'data', - builder: (context, state) => const DataSettingsPage(), - ), - GoRoute( - path: 'personalization', - builder: (context, state) => - const PersonalizationSettingsPage(), - ), - ], + routes: SettingsView.values + .map((e) => GoRoute( + path: e.name, + builder: (context, state) => e.buildContent(), + )) + .toList(), ), ], ), diff --git a/app/lib/pages/settings/accounts.dart b/app/lib/pages/settings/accounts.dart new file mode 100644 index 00000000..44e51675 --- /dev/null +++ b/app/lib/pages/settings/accounts.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:lw_file_system/lw_file_system.dart'; +import 'package:setonix/src/generated/i18n/app_localizations.dart'; +import 'package:material_leap/material_leap.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; +import 'package:setonix/services/file_system.dart'; + +import '../../bloc/settings.dart'; + +class AccountsSettingsPage extends StatefulWidget { + final bool inView; + const AccountsSettingsPage({super.key, this.inView = false}); + + @override + State createState() => _AccountsSettingsPageState(); +} + +class _AccountsSettingsPageState extends State { + late final KeyFileSystem _privateKeyFileSystem, _publicKeyFileSystem; + Future>? _keysFuture; + late final SetonixFileSystem _fileSystem; + + @override + void initState() { + super.initState(); + _fileSystem = context.read(); + _privateKeyFileSystem = _fileSystem.privateKeySystem; + _publicKeyFileSystem = _fileSystem.publicKeySystem; + _buildKeysFuture(); + } + + void _buildKeysFuture() { + _keysFuture = _privateKeyFileSystem.getKeys(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: widget.inView ? Colors.transparent : null, + appBar: WindowTitleBar( + inView: widget.inView, + backgroundColor: widget.inView ? Colors.transparent : null, + title: Text(AppLocalizations.of(context).accounts), + ), + body: FutureBuilder>( + future: _keysFuture, + builder: (context, state) { + final keys = state.data ?? []; + return ListView.builder( + itemCount: keys.length, + itemBuilder: (context, index) { + final key = keys[index]; + return Dismissible( + key: Key(key), + child: ListTile( + title: Text(key.substring(1)), + ), + onDismissed: (direction) { + _privateKeyFileSystem.deleteFile(key); + _publicKeyFileSystem.deleteFile(key); + setState(() { + keys.removeAt(index); + }); + }, + ); + }, + ); + }, + ), + floatingActionButton: FloatingActionButton.extended( + onPressed: () async { + final name = await showDialog( + context: context, builder: (context) => NameDialog()); + if (name == null) return; + await _fileSystem.generateKey(name); + setState(() { + _buildKeysFuture(); + }); + }, + label: Text(AppLocalizations.of(context).add), + icon: const PhosphorIcon(PhosphorIconsLight.plus), + ), + ); + } +} diff --git a/app/lib/pages/settings/home.dart b/app/lib/pages/settings/home.dart index 17083857..6e7f9c47 100644 --- a/app/lib/pages/settings/home.dart +++ b/app/lib/pages/settings/home.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:setonix/pages/settings/accounts.dart'; import 'package:setonix/pages/settings/input.dart'; import 'package:setonix/src/generated/i18n/app_localizations.dart'; import 'package:go_router/go_router.dart'; @@ -13,6 +14,7 @@ enum SettingsView { general, data, personalization, + accounts, inputs; bool get isEnabled => true; @@ -22,6 +24,7 @@ enum SettingsView { SettingsView.data => AppLocalizations.of(context).data, SettingsView.personalization => AppLocalizations.of(context).personalization, + SettingsView.accounts => AppLocalizations.of(context).accounts, SettingsView.inputs => AppLocalizations.of(context).inputs, }; @@ -29,9 +32,19 @@ enum SettingsView { SettingsView.general => PhosphorIcons.gear, SettingsView.data => PhosphorIcons.database, SettingsView.personalization => PhosphorIcons.monitor, + SettingsView.accounts => PhosphorIcons.user, SettingsView.inputs => PhosphorIcons.keyboard, }; String get path => '/settings/$name'; + + Widget buildContent({bool inView = false}) => switch (this) { + SettingsView.general => GeneralSettingsPage(inView: inView), + SettingsView.data => DataSettingsPage(inView: inView), + SettingsView.personalization => + PersonalizationSettingsPage(inView: inView), + SettingsView.accounts => AccountsSettingsPage(inView: inView), + SettingsView.inputs => InputsSettingsPage(inView: inView), + }; } class SettingsPage extends StatefulWidget { @@ -118,13 +131,7 @@ class _SettingsPageState extends State { if (isMobile) { return navigation; } - final content = switch (_view) { - SettingsView.general => const GeneralSettingsPage(inView: true), - SettingsView.data => const DataSettingsPage(inView: true), - SettingsView.personalization => - const PersonalizationSettingsPage(inView: true), - SettingsView.inputs => const InputsSettingsPage(inView: true), - }; + final content = _view.buildContent(inView: true); return Row(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ SizedBox(width: 300, child: navigation), Expanded(child: content), diff --git a/app/lib/services/file_system.dart b/app/lib/services/file_system.dart index 3bdbfd4e..90e42291 100644 --- a/app/lib/services/file_system.dart +++ b/app/lib/services/file_system.dart @@ -2,6 +2,7 @@ import 'dart:collection'; import 'dart:convert'; import 'dart:typed_data'; +import 'package:cryptography_plus/cryptography_plus.dart'; import 'package:file_selector/file_selector.dart' as fs; import 'package:http/http.dart' as http; import 'package:idb_shim/idb.dart'; @@ -34,12 +35,16 @@ class SetonixFileSystem { worldSystem, editorSystem; final TypedKeyFileSystem dataInfoSystem; + final KeyFileSystem privateKeySystem, publicKeySystem; static Future _onDatabaseUpgrade(VersionChangeEvent event) async { await initStores(event, ['packs', 'templates', 'worlds']); if (event.oldVersion < 2) { event.database.createObjectStore('packs-data'); } + if (event.oldVersion < 3) { + event.database.createObjectStore('keys'); + } } static const kDatabaseVersion = 2; @@ -116,6 +121,30 @@ class SetonixFileSystem { ), onDecode: SetonixData.fromData, onEncode: (data) => data.exportAsBytes(), + ), + privateKeySystem = KeyFileSystem.fromPlatform( + FileSystemConfig( + passwordStorage: SecureStoragePasswordStorage(), + storeName: 'keys', + getDirectory: (storage) async => + '${await getSetonixDirectory()}/Keys', + database: 'setonix.db', + databaseVersion: kDatabaseVersion, + keySuffix: '.key', + onDatabaseUpgrade: _onDatabaseUpgrade, + ), + ), + publicKeySystem = KeyFileSystem.fromPlatform( + FileSystemConfig( + passwordStorage: SecureStoragePasswordStorage(), + storeName: 'keys', + getDirectory: (storage) async => + '${await getSetonixDirectory()}/Keys', + database: 'setonix.db', + databaseVersion: kDatabaseVersion, + keySuffix: '.pub', + onDatabaseUpgrade: _onDatabaseUpgrade, + ), ); Future fetchCorePack() async => @@ -197,4 +226,15 @@ class SetonixFileSystem { await updateServerLastUsed(pack, serverAddress); } } + + Future generateKey(String name) async { + final generator = Ed25519(); + final keyPair = await generator.newKeyPair(); + final privateKey = await keyPair.extractPrivateKeyBytes(); + final publicKey = await keyPair.extractPublicKey(); + await privateKeySystem.createFileWithName(Uint8List.fromList(privateKey), + name: name); + await publicKeySystem + .createFileWithName(Uint8List.fromList(publicKey.bytes), name: name); + } } From 03a1c3c609a87fbc898aaeda586931bd33851117 Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Wed, 21 May 2025 22:08:55 +0200 Subject: [PATCH 02/18] Upgrade to flutter 3.32 --- .github/workflows/dart.yml | 2 +- api/lib/src/models/vector.dart | 4 +- api/pubspec.lock | 14 +- api/pubspec.yaml | 2 +- app/pubspec.lock | 34 +- app/pubspec.yaml | 3 +- docs/package.json | 12 +- docs/pnpm-lock.yaml | 623 +++++++++++---------- plugin/lib/src/rust/api/luau.dart | 2 +- plugin/lib/src/rust/api/plugin.dart | 2 +- plugin/lib/src/rust/api/simple.dart | 2 +- plugin/lib/src/rust/frb_generated.dart | 4 +- plugin/lib/src/rust/frb_generated.io.dart | 2 +- plugin/lib/src/rust/frb_generated.web.dart | 2 +- plugin/pubspec.lock | 36 +- plugin/pubspec.yaml | 4 +- plugin/rust/Cargo.lock | 16 +- plugin/rust/Cargo.toml | 2 +- plugin/rust/src/frb_generated.rs | 8 +- server/pubspec.lock | 34 +- server/pubspec.yaml | 3 +- tools/pubspec.lock | 6 +- tools/pubspec.yaml | 2 +- 23 files changed, 411 insertions(+), 408 deletions(-) diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 2f2dae4a..4f905e47 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -55,7 +55,7 @@ jobs: - name: Build flutter_rust_bridge bindings if: matrix.projects == 'plugin' run: | - cargo install flutter_rust_bridge_codegen@2.9.0 + cargo install flutter_rust_bridge_codegen@2.10.0 flutter_rust_bridge_codegen generate - name: Run build_runner if: matrix.projects == 'api' || matrix.projects == 'app' diff --git a/api/lib/src/models/vector.dart b/api/lib/src/models/vector.dart index 8cabdf5b..5f864d89 100644 --- a/api/lib/src/models/vector.dart +++ b/api/lib/src/models/vector.dart @@ -27,9 +27,9 @@ class VectorDefinition with VectorDefinitionMappable { String toDisplayString() => '($x, $y)'; - operator +(VectorDefinition other) => + VectorDefinition operator +(VectorDefinition other) => VectorDefinition(x + other.x, y + other.y); - operator -(VectorDefinition other) => + VectorDefinition operator -(VectorDefinition other) => VectorDefinition(x - other.x, y - other.y); bool inBounds(VectorDefinition first, VectorDefinition last) { diff --git a/api/pubspec.lock b/api/pubspec.lock index 3c5b0269..2e5ccd4d 100644 --- a/api/pubspec.lock +++ b/api/pubspec.lock @@ -190,10 +190,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" + sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.0" ffi: dependency: transitive description: @@ -294,10 +294,10 @@ packages: dependency: "direct dev" description: name: lints - sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "6.0.0" logging: dependency: transitive description: @@ -472,10 +472,10 @@ packages: dependency: transitive description: name: test_api - sha256: "6c7653816b1c938e121b69ff63a33c9dc68102b65a5fb0a5c0f9786256ed33e6" + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.5" + version: "0.7.6" timing: dependency: transitive description: @@ -541,4 +541,4 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.7.0 <4.0.0" + dart: ">=3.8.0-0 <4.0.0" diff --git a/api/pubspec.yaml b/api/pubspec.yaml index 1da91685..9ad415b0 100644 --- a/api/pubspec.yaml +++ b/api/pubspec.yaml @@ -31,6 +31,6 @@ dependencies: # path: ^1.8.0 dev_dependencies: - lints: ^5.0.0 + lints: ^6.0.0 dart_mappable_builder: ^4.2.3 build_runner: ^2.4.12 diff --git a/app/pubspec.lock b/app/pubspec.lock index c8cf7f84..ce17e018 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.0" barcode: dependency: "direct main" description: @@ -262,10 +262,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" + sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.0" dbus: dependency: transitive description: @@ -302,10 +302,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" ffi: dependency: transitive description: @@ -485,10 +485,10 @@ packages: dependency: transitive description: name: flutter_rust_bridge - sha256: "5a5c7a5deeef2cc2ffe6076a33b0429f4a20ceac22a397297aed2b1eb067e611" + sha256: b416ff56002789e636244fb4cc449f587656eff995e5a7169457eb0593fcaddb url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.10.0" flutter_secure_storage: dependency: "direct main" description: @@ -641,10 +641,10 @@ packages: dependency: "direct main" description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" url: "https://pub.dev" source: hosted - version: "0.19.0" + version: "0.20.2" io: dependency: transitive description: @@ -673,10 +673,10 @@ packages: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: @@ -1453,10 +1453,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.3.1" + version: "15.0.0" watcher: dependency: transitive description: @@ -1493,10 +1493,10 @@ packages: dependency: transitive description: name: webdriver - sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" + sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade" url: "https://pub.dev" source: hosted - version: "3.0.4" + version: "3.1.0" win32: dependency: transitive description: @@ -1547,4 +1547,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.7.0 <4.0.0" - flutter: ">=3.29.3" + flutter: ">=3.32.0" diff --git a/app/pubspec.yaml b/app/pubspec.yaml index a2315961..942e3b91 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: none environment: sdk: ">=3.5.0 <4.0.0" - flutter: 3.29.3 + flutter: 3.32.0 dependencies: flutter: @@ -117,6 +117,7 @@ dev_dependencies: # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter. flutter: + generate: true # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. diff --git a/docs/package.json b/docs/package.json index d6df1579..4440b994 100644 --- a/docs/package.json +++ b/docs/package.json @@ -13,21 +13,21 @@ "@astrojs/check": "^0.9.4", "@astrojs/react": "^4.2.7", "@astrojs/starlight": "^0.34.3", - "@phosphor-icons/react": "^2.1.7", - "@types/react": "^19.1.4", + "@phosphor-icons/react": "^2.1.8", + "@types/react": "^19.1.5", "@types/react-dom": "^19.1.5", - "astro": "^5.7.13", + "astro": "^5.7.14", "react": "^19.1.0", "react-dom": "^19.1.0", "remark-gemoji": "^8.0.0", "remark-heading-id": "^1.0.1", "typescript": "^5.8.3" }, - "packageManager": "pnpm@10.10.0", + "packageManager": "pnpm@10.11.0", "devDependencies": { "@vite-pwa/astro": "^1.1.0", - "sass": "^1.88.0", - "sharp": "^0.34.1", + "sass": "^1.89.0", + "sharp": "^0.34.2", "vite-plugin-pwa": "^1.0.0", "workbox-window": "^7.3.0" } diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml index 13d8daf2..3f5038d0 100644 --- a/docs/pnpm-lock.yaml +++ b/docs/pnpm-lock.yaml @@ -13,22 +13,22 @@ importers: version: 0.9.4(typescript@5.8.3) '@astrojs/react': specifier: ^4.2.7 - version: 4.2.7(@types/node@22.15.18)(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(jiti@2.4.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.88.0)(terser@5.39.2)(yaml@2.8.0) + version: 4.2.7(@types/node@22.15.21)(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(jiti@2.4.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0) '@astrojs/starlight': specifier: ^0.34.3 - version: 0.34.3(astro@5.7.13(@types/node@22.15.18)(jiti@2.4.2)(rollup@2.79.2)(sass@1.88.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0)) + version: 0.34.3(astro@5.7.14(@types/node@22.15.21)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0)) '@phosphor-icons/react': - specifier: ^2.1.7 - version: 2.1.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: ^2.1.8 + version: 2.1.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@types/react': - specifier: ^19.1.4 - version: 19.1.4 + specifier: ^19.1.5 + version: 19.1.5 '@types/react-dom': specifier: ^19.1.5 - version: 19.1.5(@types/react@19.1.4) + version: 19.1.5(@types/react@19.1.5) astro: - specifier: ^5.7.13 - version: 5.7.13(@types/node@22.15.18)(jiti@2.4.2)(rollup@2.79.2)(sass@1.88.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0) + specifier: ^5.7.14 + version: 5.7.14(@types/node@22.15.21)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0) react: specifier: ^19.1.0 version: 19.1.0 @@ -47,16 +47,16 @@ importers: devDependencies: '@vite-pwa/astro': specifier: ^1.1.0 - version: 1.1.0(astro@5.7.13(@types/node@22.15.18)(jiti@2.4.2)(rollup@2.79.2)(sass@1.88.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0))(vite-plugin-pwa@1.0.0(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(sass@1.88.0)(terser@5.39.2)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0)) + version: 1.1.0(astro@5.7.14(@types/node@22.15.21)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0))(vite-plugin-pwa@1.0.0(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0)) sass: - specifier: ^1.88.0 - version: 1.88.0 + specifier: ^1.89.0 + version: 1.89.0 sharp: - specifier: ^0.34.1 - version: 0.34.1 + specifier: ^0.34.2 + version: 0.34.2 vite-plugin-pwa: specifier: ^1.0.0 - version: 1.0.0(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(sass@1.88.0)(terser@5.39.2)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0) + version: 1.0.0(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0) workbox-window: specifier: ^7.3.0 version: 7.3.0 @@ -830,8 +830,8 @@ packages: cpu: [arm64] os: [darwin] - '@img/sharp-darwin-arm64@0.34.1': - resolution: {integrity: sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==} + '@img/sharp-darwin-arm64@0.34.2': + resolution: {integrity: sha512-OfXHZPppddivUJnqyKoi5YVeHRkkNE2zUFT2gbpKxp/JZCFYEYubnMg+gOp6lWfasPrTS+KPosKqdI+ELYVDtg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] @@ -842,8 +842,8 @@ packages: cpu: [x64] os: [darwin] - '@img/sharp-darwin-x64@0.34.1': - resolution: {integrity: sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==} + '@img/sharp-darwin-x64@0.34.2': + resolution: {integrity: sha512-dYvWqmjU9VxqXmjEtjmvHnGqF8GrVjM2Epj9rJ6BUIXvk8slvNDJbhGFvIoXzkDhrJC2jUxNLz/GUjjvSzfw+g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] @@ -939,8 +939,8 @@ packages: cpu: [arm64] os: [linux] - '@img/sharp-linux-arm64@0.34.1': - resolution: {integrity: sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==} + '@img/sharp-linux-arm64@0.34.2': + resolution: {integrity: sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] @@ -951,8 +951,8 @@ packages: cpu: [arm] os: [linux] - '@img/sharp-linux-arm@0.34.1': - resolution: {integrity: sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==} + '@img/sharp-linux-arm@0.34.2': + resolution: {integrity: sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] @@ -963,8 +963,8 @@ packages: cpu: [s390x] os: [linux] - '@img/sharp-linux-s390x@0.34.1': - resolution: {integrity: sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==} + '@img/sharp-linux-s390x@0.34.2': + resolution: {integrity: sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] @@ -975,8 +975,8 @@ packages: cpu: [x64] os: [linux] - '@img/sharp-linux-x64@0.34.1': - resolution: {integrity: sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==} + '@img/sharp-linux-x64@0.34.2': + resolution: {integrity: sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] @@ -987,8 +987,8 @@ packages: cpu: [arm64] os: [linux] - '@img/sharp-linuxmusl-arm64@0.34.1': - resolution: {integrity: sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==} + '@img/sharp-linuxmusl-arm64@0.34.2': + resolution: {integrity: sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] @@ -999,8 +999,8 @@ packages: cpu: [x64] os: [linux] - '@img/sharp-linuxmusl-x64@0.34.1': - resolution: {integrity: sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==} + '@img/sharp-linuxmusl-x64@0.34.2': + resolution: {integrity: sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] @@ -1010,19 +1010,25 @@ packages: engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] - '@img/sharp-wasm32@0.34.1': - resolution: {integrity: sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==} + '@img/sharp-wasm32@0.34.2': + resolution: {integrity: sha512-/VI4mdlJ9zkaq53MbIG6rZY+QRN3MLbR6usYlgITEzi4Rpx5S6LFKsycOQjkOGmqTNmkIdLjEvooFKwww6OpdQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] + '@img/sharp-win32-arm64@0.34.2': + resolution: {integrity: sha512-cfP/r9FdS63VA5k0xiqaNaEoGxBg9k7uE+RQGzuK9fHt7jib4zAVVseR9LsE4gJcNWgT6APKMNnCcnyOtmSEUQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + '@img/sharp-win32-ia32@0.33.5': resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-ia32@0.34.1': - resolution: {integrity: sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==} + '@img/sharp-win32-ia32@0.34.2': + resolution: {integrity: sha512-QLjGGvAbj0X/FXl8n1WbtQ6iVBpWU7JO94u/P2M4a8CFYsvQi4GW2mRy/JqkRx0qpBzaOdKJKw8uc930EX2AHw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] @@ -1033,8 +1039,8 @@ packages: cpu: [x64] os: [win32] - '@img/sharp-win32-x64@0.34.1': - resolution: {integrity: sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==} + '@img/sharp-win32-x64@0.34.2': + resolution: {integrity: sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [win32] @@ -1188,8 +1194,8 @@ packages: resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} engines: {node: '>= 10.0.0'} - '@phosphor-icons/react@2.1.7': - resolution: {integrity: sha512-g2e2eVAn1XG2a+LI09QU3IORLhnFNAFkNbo2iwbX6NOKSLOwvEMmTa7CgOzEbgNWR47z8i8kwjdvYZ5fkGx1mQ==} + '@phosphor-icons/react@2.1.8': + resolution: {integrity: sha512-RxJlAkErO+t50DsY82ga9RGOULK6Jux0MdmXqvDjtOzG3PYQFz6rjdUU2q06lPMMbJTT+d+qurKYmF7i2Uv74A==} engines: {node: '>=10'} peerDependencies: react: '>= 16.8' @@ -1244,123 +1250,123 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.40.2': - resolution: {integrity: sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==} + '@rollup/rollup-android-arm-eabi@4.41.0': + resolution: {integrity: sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.40.2': - resolution: {integrity: sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==} + '@rollup/rollup-android-arm64@4.41.0': + resolution: {integrity: sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.40.2': - resolution: {integrity: sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==} + '@rollup/rollup-darwin-arm64@4.41.0': + resolution: {integrity: sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.40.2': - resolution: {integrity: sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==} + '@rollup/rollup-darwin-x64@4.41.0': + resolution: {integrity: sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.40.2': - resolution: {integrity: sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==} + '@rollup/rollup-freebsd-arm64@4.41.0': + resolution: {integrity: sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.40.2': - resolution: {integrity: sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==} + '@rollup/rollup-freebsd-x64@4.41.0': + resolution: {integrity: sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.40.2': - resolution: {integrity: sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==} + '@rollup/rollup-linux-arm-gnueabihf@4.41.0': + resolution: {integrity: sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.40.2': - resolution: {integrity: sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==} + '@rollup/rollup-linux-arm-musleabihf@4.41.0': + resolution: {integrity: sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.40.2': - resolution: {integrity: sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==} + '@rollup/rollup-linux-arm64-gnu@4.41.0': + resolution: {integrity: sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.40.2': - resolution: {integrity: sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==} + '@rollup/rollup-linux-arm64-musl@4.41.0': + resolution: {integrity: sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.40.2': - resolution: {integrity: sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==} + '@rollup/rollup-linux-loongarch64-gnu@4.41.0': + resolution: {integrity: sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.40.2': - resolution: {integrity: sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==} + '@rollup/rollup-linux-powerpc64le-gnu@4.41.0': + resolution: {integrity: sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.40.2': - resolution: {integrity: sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==} + '@rollup/rollup-linux-riscv64-gnu@4.41.0': + resolution: {integrity: sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.40.2': - resolution: {integrity: sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==} + '@rollup/rollup-linux-riscv64-musl@4.41.0': + resolution: {integrity: sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.40.2': - resolution: {integrity: sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==} + '@rollup/rollup-linux-s390x-gnu@4.41.0': + resolution: {integrity: sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.40.2': - resolution: {integrity: sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==} + '@rollup/rollup-linux-x64-gnu@4.41.0': + resolution: {integrity: sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.40.2': - resolution: {integrity: sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==} + '@rollup/rollup-linux-x64-musl@4.41.0': + resolution: {integrity: sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.40.2': - resolution: {integrity: sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==} + '@rollup/rollup-win32-arm64-msvc@4.41.0': + resolution: {integrity: sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.40.2': - resolution: {integrity: sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==} + '@rollup/rollup-win32-ia32-msvc@4.41.0': + resolution: {integrity: sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.40.2': - resolution: {integrity: sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA==} + '@rollup/rollup-win32-x64-msvc@4.41.0': + resolution: {integrity: sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==} cpu: [x64] os: [win32] - '@shikijs/core@3.4.1': - resolution: {integrity: sha512-GCqSd3KXRTKX1sViP7fIyyyf6do2QVg+fTd4IT00ucYCVSKiSN8HbFbfyjGsoZePNKWcQqXe4U4rrz2IVldG5A==} + '@shikijs/core@3.4.2': + resolution: {integrity: sha512-AG8vnSi1W2pbgR2B911EfGqtLE9c4hQBYkv/x7Z+Kt0VxhgQKcW7UNDVYsu9YxwV6u+OJrvdJrMq6DNWoBjihQ==} - '@shikijs/engine-javascript@3.4.1': - resolution: {integrity: sha512-oGvRqN3Bsk+cGzmCb/5Kt/LfD7uyA8vCUUawyqmLti/AYNV7++zIZFEW8JwW5PrpPNWWx9RcZ/chnYLedzlVIQ==} + '@shikijs/engine-javascript@3.4.2': + resolution: {integrity: sha512-1/adJbSMBOkpScCE/SB6XkjJU17ANln3Wky7lOmrnpl+zBdQ1qXUJg2GXTYVHRq+2j3hd1DesmElTXYDgtfSOQ==} - '@shikijs/engine-oniguruma@3.4.1': - resolution: {integrity: sha512-p8I5KWgEDUcXRif9JjJUZtNeqCyxZ8xcslecDJMigsqSZfokwqQIsH4aGpdjzmDf8LIWvT+C3TCxnJQVaPmCbQ==} + '@shikijs/engine-oniguruma@3.4.2': + resolution: {integrity: sha512-zcZKMnNndgRa3ORja6Iemsr3DrLtkX3cAF7lTJkdMB6v9alhlBsX9uNiCpqofNrXOvpA3h6lHcLJxgCIhVOU5Q==} - '@shikijs/langs@3.4.1': - resolution: {integrity: sha512-v5A5ApJYcrcPLHcwAi0bViUU+Unh67UaXU9gGX3qfr2z3AqlqSZbC00W/3J4+tfGJASzwrWDro2R1er6SsCL1Q==} + '@shikijs/langs@3.4.2': + resolution: {integrity: sha512-H6azIAM+OXD98yztIfs/KH5H4PU39t+SREhmM8LaNXyUrqj2mx+zVkr8MWYqjceSjDw9I1jawm1WdFqU806rMA==} - '@shikijs/themes@3.4.1': - resolution: {integrity: sha512-XOJgs55mVVMZtNVJx1NVmdcfXG9HIyZGh7qpCw/Ok5UMjWgkmb8z15TgcmF3ItvHItijiIMl9BLcNO/tFSGl1w==} + '@shikijs/themes@3.4.2': + resolution: {integrity: sha512-qAEuAQh+brd8Jyej2UDDf+b4V2g1Rm8aBIdvt32XhDPrHvDkEnpb7Kzc9hSuHUxz0Iuflmq7elaDuQAP9bHIhg==} - '@shikijs/types@3.4.1': - resolution: {integrity: sha512-4flT+pToGqRBb0UhGqXTV7rCqUS3fhc8z3S2Djc3E5USKhXwadeKGFVNB2rKXfohlrEozNJMtMiZaN8lfdj/ZQ==} + '@shikijs/types@3.4.2': + resolution: {integrity: sha512-zHC1l7L+eQlDXLnxvM9R91Efh2V4+rN3oMVS2swCBssbj2U/FBwybD1eeLaq8yl/iwT+zih8iUbTBCgGZOYlVg==} '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} @@ -1419,16 +1425,16 @@ packages: '@types/node@17.0.45': resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} - '@types/node@22.15.18': - resolution: {integrity: sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg==} + '@types/node@22.15.21': + resolution: {integrity: sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==} '@types/react-dom@19.1.5': resolution: {integrity: sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==} peerDependencies: '@types/react': ^19.0.0 - '@types/react@19.1.4': - resolution: {integrity: sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g==} + '@types/react@19.1.5': + resolution: {integrity: sha512-piErsCVVbpMMT2r7wbawdZsq4xMvIAhQuac2gedQHysu1TZYEigE6pnFfgZT+/jQnrRuF5r+SHzuehFjfRjr4g==} '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -1464,25 +1470,25 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 - '@volar/kit@2.4.13': - resolution: {integrity: sha512-x5b2JwVT+0YQcIR4uX0NaW9Ocf3ShQRvoA18OK9ZYoFyqIYnK/niuLc8uI7hcVaey2S3EPBe3QvLGD69DJ/t6Q==} + '@volar/kit@2.4.14': + resolution: {integrity: sha512-kBcmHjEodtmYGJELHePZd2JdeYm4ZGOd9F/pQ1YETYIzAwy4Z491EkJ1nRSo/GTxwKt0XYwYA/dHSEgXecVHRA==} peerDependencies: typescript: '*' - '@volar/language-core@2.4.13': - resolution: {integrity: sha512-MnQJ7eKchJx5Oz+YdbqyFUk8BN6jasdJv31n/7r6/WwlOOv7qzvot6B66887l2ST3bUW4Mewml54euzpJWA6bg==} + '@volar/language-core@2.4.14': + resolution: {integrity: sha512-X6beusV0DvuVseaOEy7GoagS4rYHgDHnTrdOj5jeUb49fW5ceQyP9Ej5rBhqgz2wJggl+2fDbbojq1XKaxDi6w==} - '@volar/language-server@2.4.13': - resolution: {integrity: sha512-g8ucG5+FJgQT2r+Te1Pk+ppoPHCwzJ54gq/oN1utjtA3+iE9hp5E+M60Ks+hhGrexUPC/E3EABDQlCagmEal+Q==} + '@volar/language-server@2.4.14': + resolution: {integrity: sha512-P3mGbQbW0v40UYBnb3DAaNtRYx6/MGOVKzdOWmBCGwjUkCR2xBkGrCFt05XnPDwFS/cTWDh2U6Mc9lpZ8Aecfw==} - '@volar/language-service@2.4.13': - resolution: {integrity: sha512-yngNLIxt1w3S60YLTRAa7MSE1IRmXcxGA9ttLjndY0Jc3owCFjeAWSPeXBILZBJOtdT8rP07JY1ozwUls/gvRg==} + '@volar/language-service@2.4.14': + resolution: {integrity: sha512-vNC3823EJohdzLTyjZoCMPwoWCfINB5emusniCkW5CGoGHQov4VVmT6yI5ncgP/NpgAIUv2NEkJooXvLHA4VeQ==} - '@volar/source-map@2.4.13': - resolution: {integrity: sha512-l/EBcc2FkvHgz2ZxV+OZK3kMSroMr7nN3sZLF2/f6kWW66q8+tEL4giiYyFjt0BcubqJhBt6soYIrAPhg/Yr+Q==} + '@volar/source-map@2.4.14': + resolution: {integrity: sha512-5TeKKMh7Sfxo8021cJfmBzcjfY1SsXsPMMjMvjY7ivesdnybqqS+GxGAoXHAOUawQTwtdUxgP65Im+dEmvWtYQ==} - '@volar/typescript@2.4.13': - resolution: {integrity: sha512-Ukz4xv84swJPupZeoFsQoeJEOm7U9pqsEnaGGgt5ni3SCTa22m8oJP5Nng3Wed7Uw5RBELdLxxORX8YhJPyOgQ==} + '@volar/typescript@2.4.14': + resolution: {integrity: sha512-p8Z6f/bZM3/HyCdRNFZOEEzts51uV8WHeN8Tnfnm2EBv6FDB2TQLzfVx7aJvnl8ofKAOnS64B2O8bImBFaauRw==} '@vscode/emmet-helper@2.11.0': resolution: {integrity: sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==} @@ -1556,8 +1562,8 @@ packages: peerDependencies: astro: ^4.0.0-beta || ^5.0.0-beta || ^3.3.0 - astro@5.7.13: - resolution: {integrity: sha512-cRGq2llKOhV3XMcYwQpfBIUcssN6HEK5CRbcMxAfd9OcFhvWE7KUy50zLioAZVVl3AqgUTJoNTlmZfD2eG0G1w==} + astro@5.7.14: + resolution: {integrity: sha512-DfuDD49f7mbHB7ygLm8KXEC6QQtpLoNrmoylcMLKdl1ahXNdiw+mgW8ApEMyHTUyVrqEUnr4gZCKSlZ9POCHjQ==} engines: {node: ^18.17.1 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} hasBin: true @@ -1873,8 +1879,8 @@ packages: engines: {node: '>=0.10.0'} hasBin: true - electron-to-chromium@1.5.154: - resolution: {integrity: sha512-G4VCFAyKbp1QJ+sWdXYIRYsPGvlV5sDACfCmoMFog3rjm1syLhI41WXm/swZypwCIWIm4IFLWzHY14joWMQ5Fw==} + electron-to-chromium@1.5.155: + resolution: {integrity: sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==} emmet@2.4.11: resolution: {integrity: sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==} @@ -3012,8 +3018,8 @@ packages: engines: {node: '>=10.0.0'} hasBin: true - rollup@4.40.2: - resolution: {integrity: sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==} + rollup@4.41.0: + resolution: {integrity: sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -3035,8 +3041,8 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} - sass@1.88.0: - resolution: {integrity: sha512-sF6TWQqjFvr4JILXzG4ucGOLELkESHL+I5QJhh7CNaE+Yge0SI+ehCatsXhJ7ymU1hAFcIS3/PBpjdIbXoyVbg==} + sass@1.89.0: + resolution: {integrity: sha512-ld+kQU8YTdGNjOLfRWBzewJpU5cwEv/h5yyqlSeJcj6Yh8U4TDA9UA5FPicqDz/xgRPWRSYIQNiFks21TbA9KQ==} engines: {node: '>=14.0.0'} hasBin: true @@ -3074,12 +3080,12 @@ packages: resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - sharp@0.34.1: - resolution: {integrity: sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==} + sharp@0.34.2: + resolution: {integrity: sha512-lszvBmB9QURERtyKT2bNmsgxXK0ShJrL/fvqlonCo7e6xBF8nT8xU6pW+PMIbLsz0RxQk3rgH9kd8UmvOzlMJg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - shiki@3.4.1: - resolution: {integrity: sha512-PSnoczt+iWIOB4iRQ+XVPFtTuN1FcmuYzPgUBZTSv5pC6CozssIx2M4O5n4S9gJlUu9A3FxMU0ZPaHflky/6LA==} + shiki@3.4.2: + resolution: {integrity: sha512-wuxzZzQG8kvZndD7nustrNFIKYJ1jJoWIPaBpVe2+KHSvtzMi4SBjOxrigs8qeqce/l3U0cwiC+VAkLKSunHQQ==} side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} @@ -3240,8 +3246,8 @@ packages: trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - tsconfck@3.1.5: - resolution: {integrity: sha512-CLDfGgUp7XPswWnezWwsCRxNmgQjhYq3VXHM0/XIRxhVrKw0M1if9agzryh1QS3nxjCROvV+xWxoJO1YctzzWg==} + tsconfck@3.1.6: + resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} engines: {node: ^18 || >=20} hasBin: true peerDependencies: @@ -3280,8 +3286,8 @@ packages: typesafe-path@0.2.2: resolution: {integrity: sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==} - typescript-auto-import-cache@0.3.5: - resolution: {integrity: sha512-fAIveQKsoYj55CozUiBoj4b/7WpN0i4o74wiGY5JVUEoD0XiqDk1tJqTEjgzL2/AizKQrXxyRosSebyDzBZKjw==} + typescript-auto-import-cache@0.3.6: + resolution: {integrity: sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ==} typescript@5.8.3: resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} @@ -3760,8 +3766,8 @@ packages: resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==} engines: {node: '>=12.20'} - yocto-spinner@0.2.2: - resolution: {integrity: sha512-21rPcM3e4vCpOXThiFRByX8amU5By1R0wNS8Oex+DP3YgC8xdU0vEJ/K8cbPLiIJVosSSysgcFof6s6MSD5/Vw==} + yocto-spinner@0.2.3: + resolution: {integrity: sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==} engines: {node: '>=18.19'} yoctocolors@2.1.1: @@ -3779,8 +3785,8 @@ packages: typescript: ^4.9.4 || ^5.0.2 zod: ^3 - zod@3.24.4: - resolution: {integrity: sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==} + zod@3.25.17: + resolution: {integrity: sha512-8hQzQ/kMOIFbwOgPrm9Sf9rtFHpFUMy4HvN0yEB0spw14aYi0uT5xG5CE2DB9cd51GWNsz+DNO7se1kztHMKnw==} zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -3819,19 +3825,19 @@ snapshots: '@astrojs/compiler': 2.12.0 '@astrojs/yaml2ts': 0.2.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@volar/kit': 2.4.13(typescript@5.8.3) - '@volar/language-core': 2.4.13 - '@volar/language-server': 2.4.13 - '@volar/language-service': 2.4.13 + '@volar/kit': 2.4.14(typescript@5.8.3) + '@volar/language-core': 2.4.14 + '@volar/language-server': 2.4.14 + '@volar/language-service': 2.4.14 fast-glob: 3.3.3 muggle-string: 0.4.1 - volar-service-css: 0.0.62(@volar/language-service@2.4.13) - volar-service-emmet: 0.0.62(@volar/language-service@2.4.13) - volar-service-html: 0.0.62(@volar/language-service@2.4.13) - volar-service-prettier: 0.0.62(@volar/language-service@2.4.13) - volar-service-typescript: 0.0.62(@volar/language-service@2.4.13) - volar-service-typescript-twoslash-queries: 0.0.62(@volar/language-service@2.4.13) - volar-service-yaml: 0.0.62(@volar/language-service@2.4.13) + volar-service-css: 0.0.62(@volar/language-service@2.4.14) + volar-service-emmet: 0.0.62(@volar/language-service@2.4.14) + volar-service-html: 0.0.62(@volar/language-service@2.4.14) + volar-service-prettier: 0.0.62(@volar/language-service@2.4.14) + volar-service-typescript: 0.0.62(@volar/language-service@2.4.14) + volar-service-typescript-twoslash-queries: 0.0.62(@volar/language-service@2.4.14) + volar-service-yaml: 0.0.62(@volar/language-service@2.4.14) vscode-html-languageservice: 5.4.0 vscode-uri: 3.1.0 transitivePeerDependencies: @@ -3853,7 +3859,7 @@ snapshots: remark-parse: 11.0.0 remark-rehype: 11.1.2 remark-smartypants: 3.0.2 - shiki: 3.4.1 + shiki: 3.4.2 smol-toml: 1.3.4 unified: 11.0.5 unist-util-remove-position: 5.0.0 @@ -3863,12 +3869,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/mdx@4.2.6(astro@5.7.13(@types/node@22.15.18)(jiti@2.4.2)(rollup@2.79.2)(sass@1.88.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0))': + '@astrojs/mdx@4.2.6(astro@5.7.14(@types/node@22.15.21)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0))': dependencies: '@astrojs/markdown-remark': 6.3.1 '@mdx-js/mdx': 3.1.0(acorn@8.14.1) acorn: 8.14.1 - astro: 5.7.13(@types/node@22.15.18)(jiti@2.4.2)(rollup@2.79.2)(sass@1.88.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0) + astro: 5.7.14(@types/node@22.15.21)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0) es-module-lexer: 1.7.0 estree-util-visit: 2.0.0 hast-util-to-html: 9.0.5 @@ -3886,15 +3892,15 @@ snapshots: dependencies: prismjs: 1.30.0 - '@astrojs/react@4.2.7(@types/node@22.15.18)(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(jiti@2.4.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.88.0)(terser@5.39.2)(yaml@2.8.0)': + '@astrojs/react@4.2.7(@types/node@22.15.21)(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(jiti@2.4.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)': dependencies: - '@types/react': 19.1.4 - '@types/react-dom': 19.1.5(@types/react@19.1.4) - '@vitejs/plugin-react': 4.4.1(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(sass@1.88.0)(terser@5.39.2)(yaml@2.8.0)) + '@types/react': 19.1.5 + '@types/react-dom': 19.1.5(@types/react@19.1.5) + '@vitejs/plugin-react': 4.4.1(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) ultrahtml: 1.6.0 - vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(sass@1.88.0)(terser@5.39.2)(yaml@2.8.0) + vite: 6.3.5(@types/node@22.15.21)(jiti@2.4.2)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0) transitivePeerDependencies: - '@types/node' - jiti @@ -3913,19 +3919,19 @@ snapshots: dependencies: sitemap: 8.0.0 stream-replace-string: 2.0.0 - zod: 3.24.4 + zod: 3.25.17 - '@astrojs/starlight@0.34.3(astro@5.7.13(@types/node@22.15.18)(jiti@2.4.2)(rollup@2.79.2)(sass@1.88.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0))': + '@astrojs/starlight@0.34.3(astro@5.7.14(@types/node@22.15.21)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0))': dependencies: '@astrojs/markdown-remark': 6.3.1 - '@astrojs/mdx': 4.2.6(astro@5.7.13(@types/node@22.15.18)(jiti@2.4.2)(rollup@2.79.2)(sass@1.88.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0)) + '@astrojs/mdx': 4.2.6(astro@5.7.14(@types/node@22.15.21)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0)) '@astrojs/sitemap': 3.4.0 '@pagefind/default-ui': 1.3.0 '@types/hast': 3.0.4 '@types/js-yaml': 4.0.9 '@types/mdast': 4.0.4 - astro: 5.7.13(@types/node@22.15.18)(jiti@2.4.2)(rollup@2.79.2)(sass@1.88.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0) - astro-expressive-code: 0.41.2(astro@5.7.13(@types/node@22.15.18)(jiti@2.4.2)(rollup@2.79.2)(sass@1.88.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0)) + astro: 5.7.14(@types/node@22.15.21)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0) + astro-expressive-code: 0.41.2(astro@5.7.14(@types/node@22.15.21)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0)) bcp-47: 2.1.0 hast-util-from-html: 2.0.3 hast-util-select: 6.0.4 @@ -4743,7 +4749,7 @@ snapshots: '@expressive-code/plugin-shiki@0.41.2': dependencies: '@expressive-code/core': 0.41.2 - shiki: 3.4.1 + shiki: 3.4.2 '@expressive-code/plugin-text-markers@0.41.2': dependencies: @@ -4754,7 +4760,7 @@ snapshots: '@img/sharp-libvips-darwin-arm64': 1.0.4 optional: true - '@img/sharp-darwin-arm64@0.34.1': + '@img/sharp-darwin-arm64@0.34.2': optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.1.0 optional: true @@ -4764,7 +4770,7 @@ snapshots: '@img/sharp-libvips-darwin-x64': 1.0.4 optional: true - '@img/sharp-darwin-x64@0.34.1': + '@img/sharp-darwin-x64@0.34.2': optionalDependencies: '@img/sharp-libvips-darwin-x64': 1.1.0 optional: true @@ -4825,7 +4831,7 @@ snapshots: '@img/sharp-libvips-linux-arm64': 1.0.4 optional: true - '@img/sharp-linux-arm64@0.34.1': + '@img/sharp-linux-arm64@0.34.2': optionalDependencies: '@img/sharp-libvips-linux-arm64': 1.1.0 optional: true @@ -4835,7 +4841,7 @@ snapshots: '@img/sharp-libvips-linux-arm': 1.0.5 optional: true - '@img/sharp-linux-arm@0.34.1': + '@img/sharp-linux-arm@0.34.2': optionalDependencies: '@img/sharp-libvips-linux-arm': 1.1.0 optional: true @@ -4845,7 +4851,7 @@ snapshots: '@img/sharp-libvips-linux-s390x': 1.0.4 optional: true - '@img/sharp-linux-s390x@0.34.1': + '@img/sharp-linux-s390x@0.34.2': optionalDependencies: '@img/sharp-libvips-linux-s390x': 1.1.0 optional: true @@ -4855,7 +4861,7 @@ snapshots: '@img/sharp-libvips-linux-x64': 1.0.4 optional: true - '@img/sharp-linux-x64@0.34.1': + '@img/sharp-linux-x64@0.34.2': optionalDependencies: '@img/sharp-libvips-linux-x64': 1.1.0 optional: true @@ -4865,7 +4871,7 @@ snapshots: '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 optional: true - '@img/sharp-linuxmusl-arm64@0.34.1': + '@img/sharp-linuxmusl-arm64@0.34.2': optionalDependencies: '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 optional: true @@ -4875,7 +4881,7 @@ snapshots: '@img/sharp-libvips-linuxmusl-x64': 1.0.4 optional: true - '@img/sharp-linuxmusl-x64@0.34.1': + '@img/sharp-linuxmusl-x64@0.34.2': optionalDependencies: '@img/sharp-libvips-linuxmusl-x64': 1.1.0 optional: true @@ -4885,21 +4891,24 @@ snapshots: '@emnapi/runtime': 1.4.3 optional: true - '@img/sharp-wasm32@0.34.1': + '@img/sharp-wasm32@0.34.2': dependencies: '@emnapi/runtime': 1.4.3 optional: true + '@img/sharp-win32-arm64@0.34.2': + optional: true + '@img/sharp-win32-ia32@0.33.5': optional: true - '@img/sharp-win32-ia32@0.34.1': + '@img/sharp-win32-ia32@0.34.2': optional: true '@img/sharp-win32-x64@0.33.5': optional: true - '@img/sharp-win32-x64@0.34.1': + '@img/sharp-win32-x64@0.34.2': optional: true '@jridgewell/gen-mapping@0.3.8': @@ -5046,7 +5055,7 @@ snapshots: '@parcel/watcher-win32-x64': 2.5.1 optional: true - '@phosphor-icons/react@2.1.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@phosphor-icons/react@2.1.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) @@ -5101,93 +5110,93 @@ snapshots: optionalDependencies: rollup: 2.79.2 - '@rollup/rollup-android-arm-eabi@4.40.2': + '@rollup/rollup-android-arm-eabi@4.41.0': optional: true - '@rollup/rollup-android-arm64@4.40.2': + '@rollup/rollup-android-arm64@4.41.0': optional: true - '@rollup/rollup-darwin-arm64@4.40.2': + '@rollup/rollup-darwin-arm64@4.41.0': optional: true - '@rollup/rollup-darwin-x64@4.40.2': + '@rollup/rollup-darwin-x64@4.41.0': optional: true - '@rollup/rollup-freebsd-arm64@4.40.2': + '@rollup/rollup-freebsd-arm64@4.41.0': optional: true - '@rollup/rollup-freebsd-x64@4.40.2': + '@rollup/rollup-freebsd-x64@4.41.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.40.2': + '@rollup/rollup-linux-arm-gnueabihf@4.41.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.40.2': + '@rollup/rollup-linux-arm-musleabihf@4.41.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.40.2': + '@rollup/rollup-linux-arm64-gnu@4.41.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.40.2': + '@rollup/rollup-linux-arm64-musl@4.41.0': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.40.2': + '@rollup/rollup-linux-loongarch64-gnu@4.41.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.40.2': + '@rollup/rollup-linux-powerpc64le-gnu@4.41.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.40.2': + '@rollup/rollup-linux-riscv64-gnu@4.41.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.40.2': + '@rollup/rollup-linux-riscv64-musl@4.41.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.40.2': + '@rollup/rollup-linux-s390x-gnu@4.41.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.40.2': + '@rollup/rollup-linux-x64-gnu@4.41.0': optional: true - '@rollup/rollup-linux-x64-musl@4.40.2': + '@rollup/rollup-linux-x64-musl@4.41.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.40.2': + '@rollup/rollup-win32-arm64-msvc@4.41.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.40.2': + '@rollup/rollup-win32-ia32-msvc@4.41.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.40.2': + '@rollup/rollup-win32-x64-msvc@4.41.0': optional: true - '@shikijs/core@3.4.1': + '@shikijs/core@3.4.2': dependencies: - '@shikijs/types': 3.4.1 + '@shikijs/types': 3.4.2 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 hast-util-to-html: 9.0.5 - '@shikijs/engine-javascript@3.4.1': + '@shikijs/engine-javascript@3.4.2': dependencies: - '@shikijs/types': 3.4.1 + '@shikijs/types': 3.4.2 '@shikijs/vscode-textmate': 10.0.2 oniguruma-to-es: 4.3.3 - '@shikijs/engine-oniguruma@3.4.1': + '@shikijs/engine-oniguruma@3.4.2': dependencies: - '@shikijs/types': 3.4.1 + '@shikijs/types': 3.4.2 '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/langs@3.4.1': + '@shikijs/langs@3.4.2': dependencies: - '@shikijs/types': 3.4.1 + '@shikijs/types': 3.4.2 - '@shikijs/themes@3.4.1': + '@shikijs/themes@3.4.2': dependencies: - '@shikijs/types': 3.4.1 + '@shikijs/types': 3.4.2 - '@shikijs/types@3.4.1': + '@shikijs/types@3.4.2': dependencies: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -5240,7 +5249,7 @@ snapshots: '@types/fontkit@2.0.8': dependencies: - '@types/node': 22.15.18 + '@types/node': 22.15.21 '@types/hast@3.0.4': dependencies: @@ -5262,15 +5271,15 @@ snapshots: '@types/node@17.0.45': {} - '@types/node@22.15.18': + '@types/node@22.15.21': dependencies: undici-types: 6.21.0 - '@types/react-dom@19.1.5(@types/react@19.1.4)': + '@types/react-dom@19.1.5(@types/react@19.1.5)': dependencies: - '@types/react': 19.1.4 + '@types/react': 19.1.5 - '@types/react@19.1.4': + '@types/react@19.1.5': dependencies: csstype: 3.1.3 @@ -5288,40 +5297,40 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vite-pwa/astro@1.1.0(astro@5.7.13(@types/node@22.15.18)(jiti@2.4.2)(rollup@2.79.2)(sass@1.88.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0))(vite-plugin-pwa@1.0.0(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(sass@1.88.0)(terser@5.39.2)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0))': + '@vite-pwa/astro@1.1.0(astro@5.7.14(@types/node@22.15.21)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0))(vite-plugin-pwa@1.0.0(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0))': dependencies: - astro: 5.7.13(@types/node@22.15.18)(jiti@2.4.2)(rollup@2.79.2)(sass@1.88.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0) - vite-plugin-pwa: 1.0.0(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(sass@1.88.0)(terser@5.39.2)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0) + astro: 5.7.14(@types/node@22.15.21)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0) + vite-plugin-pwa: 1.0.0(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0) - '@vitejs/plugin-react@4.4.1(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(sass@1.88.0)(terser@5.39.2)(yaml@2.8.0))': + '@vitejs/plugin-react@4.4.1(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))': dependencies: '@babel/core': 7.27.1 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.1) '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.27.1) '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(sass@1.88.0)(terser@5.39.2)(yaml@2.8.0) + vite: 6.3.5(@types/node@22.15.21)(jiti@2.4.2)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0) transitivePeerDependencies: - supports-color - '@volar/kit@2.4.13(typescript@5.8.3)': + '@volar/kit@2.4.14(typescript@5.8.3)': dependencies: - '@volar/language-service': 2.4.13 - '@volar/typescript': 2.4.13 + '@volar/language-service': 2.4.14 + '@volar/typescript': 2.4.14 typesafe-path: 0.2.2 typescript: 5.8.3 vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.1.0 - '@volar/language-core@2.4.13': + '@volar/language-core@2.4.14': dependencies: - '@volar/source-map': 2.4.13 + '@volar/source-map': 2.4.14 - '@volar/language-server@2.4.13': + '@volar/language-server@2.4.14': dependencies: - '@volar/language-core': 2.4.13 - '@volar/language-service': 2.4.13 - '@volar/typescript': 2.4.13 + '@volar/language-core': 2.4.14 + '@volar/language-service': 2.4.14 + '@volar/typescript': 2.4.14 path-browserify: 1.0.1 request-light: 0.7.0 vscode-languageserver: 9.0.1 @@ -5329,18 +5338,18 @@ snapshots: vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.1.0 - '@volar/language-service@2.4.13': + '@volar/language-service@2.4.14': dependencies: - '@volar/language-core': 2.4.13 + '@volar/language-core': 2.4.14 vscode-languageserver-protocol: 3.17.5 vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.1.0 - '@volar/source-map@2.4.13': {} + '@volar/source-map@2.4.14': {} - '@volar/typescript@2.4.13': + '@volar/typescript@2.4.14': dependencies: - '@volar/language-core': 2.4.13 + '@volar/language-core': 2.4.14 path-browserify: 1.0.1 vscode-uri: 3.1.0 @@ -5411,12 +5420,12 @@ snapshots: astring@1.9.0: {} - astro-expressive-code@0.41.2(astro@5.7.13(@types/node@22.15.18)(jiti@2.4.2)(rollup@2.79.2)(sass@1.88.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0)): + astro-expressive-code@0.41.2(astro@5.7.14(@types/node@22.15.21)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0)): dependencies: - astro: 5.7.13(@types/node@22.15.18)(jiti@2.4.2)(rollup@2.79.2)(sass@1.88.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0) + astro: 5.7.14(@types/node@22.15.21)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0) rehype-expressive-code: 0.41.2 - astro@5.7.13(@types/node@22.15.18)(jiti@2.4.2)(rollup@2.79.2)(sass@1.88.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0): + astro@5.7.14(@types/node@22.15.21)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.0)(terser@5.39.2)(typescript@5.8.3)(yaml@2.8.0): dependencies: '@astrojs/compiler': 2.12.0 '@astrojs/internal-helpers': 0.6.1 @@ -5448,6 +5457,7 @@ snapshots: github-slugger: 2.0.0 html-escaper: 3.0.3 http-cache-semantics: 4.2.0 + import-meta-resolve: 4.1.0 js-yaml: 4.1.0 kleur: 4.1.5 magic-string: 0.30.17 @@ -5461,23 +5471,23 @@ snapshots: prompts: 2.4.2 rehype: 13.0.2 semver: 7.7.2 - shiki: 3.4.1 + shiki: 3.4.2 tinyexec: 0.3.2 tinyglobby: 0.2.13 - tsconfck: 3.1.5(typescript@5.8.3) + tsconfck: 3.1.6(typescript@5.8.3) ultrahtml: 1.6.0 unifont: 0.5.0 unist-util-visit: 5.0.0 unstorage: 1.16.0 vfile: 6.0.3 - vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(sass@1.88.0)(terser@5.39.2)(yaml@2.8.0) - vitefu: 1.0.6(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(sass@1.88.0)(terser@5.39.2)(yaml@2.8.0)) + vite: 6.3.5(@types/node@22.15.21)(jiti@2.4.2)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0) + vitefu: 1.0.6(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)) xxhash-wasm: 1.1.0 yargs-parser: 21.1.1 - yocto-spinner: 0.2.2 - zod: 3.24.4 - zod-to-json-schema: 3.24.5(zod@3.24.4) - zod-to-ts: 1.2.0(typescript@5.8.3)(zod@3.24.4) + yocto-spinner: 0.2.3 + zod: 3.25.17 + zod-to-json-schema: 3.24.5(zod@3.25.17) + zod-to-ts: 1.2.0(typescript@5.8.3)(zod@3.25.17) optionalDependencies: sharp: 0.33.5 transitivePeerDependencies: @@ -5602,7 +5612,7 @@ snapshots: browserslist@4.24.5: dependencies: caniuse-lite: 1.0.30001718 - electron-to-chromium: 1.5.154 + electron-to-chromium: 1.5.155 node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.24.5) @@ -5806,7 +5816,7 @@ snapshots: dependencies: jake: 10.9.2 - electron-to-chromium@1.5.154: {} + electron-to-chromium@1.5.155: {} emmet@2.4.11: dependencies: @@ -7509,30 +7519,30 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - rollup@4.40.2: + rollup@4.41.0: dependencies: '@types/estree': 1.0.7 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.40.2 - '@rollup/rollup-android-arm64': 4.40.2 - '@rollup/rollup-darwin-arm64': 4.40.2 - '@rollup/rollup-darwin-x64': 4.40.2 - '@rollup/rollup-freebsd-arm64': 4.40.2 - '@rollup/rollup-freebsd-x64': 4.40.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.40.2 - '@rollup/rollup-linux-arm-musleabihf': 4.40.2 - '@rollup/rollup-linux-arm64-gnu': 4.40.2 - '@rollup/rollup-linux-arm64-musl': 4.40.2 - '@rollup/rollup-linux-loongarch64-gnu': 4.40.2 - '@rollup/rollup-linux-powerpc64le-gnu': 4.40.2 - '@rollup/rollup-linux-riscv64-gnu': 4.40.2 - '@rollup/rollup-linux-riscv64-musl': 4.40.2 - '@rollup/rollup-linux-s390x-gnu': 4.40.2 - '@rollup/rollup-linux-x64-gnu': 4.40.2 - '@rollup/rollup-linux-x64-musl': 4.40.2 - '@rollup/rollup-win32-arm64-msvc': 4.40.2 - '@rollup/rollup-win32-ia32-msvc': 4.40.2 - '@rollup/rollup-win32-x64-msvc': 4.40.2 + '@rollup/rollup-android-arm-eabi': 4.41.0 + '@rollup/rollup-android-arm64': 4.41.0 + '@rollup/rollup-darwin-arm64': 4.41.0 + '@rollup/rollup-darwin-x64': 4.41.0 + '@rollup/rollup-freebsd-arm64': 4.41.0 + '@rollup/rollup-freebsd-x64': 4.41.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.41.0 + '@rollup/rollup-linux-arm-musleabihf': 4.41.0 + '@rollup/rollup-linux-arm64-gnu': 4.41.0 + '@rollup/rollup-linux-arm64-musl': 4.41.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.41.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.41.0 + '@rollup/rollup-linux-riscv64-gnu': 4.41.0 + '@rollup/rollup-linux-riscv64-musl': 4.41.0 + '@rollup/rollup-linux-s390x-gnu': 4.41.0 + '@rollup/rollup-linux-x64-gnu': 4.41.0 + '@rollup/rollup-linux-x64-musl': 4.41.0 + '@rollup/rollup-win32-arm64-msvc': 4.41.0 + '@rollup/rollup-win32-ia32-msvc': 4.41.0 + '@rollup/rollup-win32-x64-msvc': 4.41.0 fsevents: 2.3.3 run-parallel@1.2.0: @@ -7560,7 +7570,7 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 - sass@1.88.0: + sass@1.89.0: dependencies: chokidar: 4.0.3 immutable: 5.1.2 @@ -7629,14 +7639,14 @@ snapshots: '@img/sharp-win32-x64': 0.33.5 optional: true - sharp@0.34.1: + sharp@0.34.2: dependencies: color: 4.2.3 detect-libc: 2.0.4 semver: 7.7.2 optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.1 - '@img/sharp-darwin-x64': 0.34.1 + '@img/sharp-darwin-arm64': 0.34.2 + '@img/sharp-darwin-x64': 0.34.2 '@img/sharp-libvips-darwin-arm64': 1.1.0 '@img/sharp-libvips-darwin-x64': 1.1.0 '@img/sharp-libvips-linux-arm': 1.1.0 @@ -7646,24 +7656,25 @@ snapshots: '@img/sharp-libvips-linux-x64': 1.1.0 '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 '@img/sharp-libvips-linuxmusl-x64': 1.1.0 - '@img/sharp-linux-arm': 0.34.1 - '@img/sharp-linux-arm64': 0.34.1 - '@img/sharp-linux-s390x': 0.34.1 - '@img/sharp-linux-x64': 0.34.1 - '@img/sharp-linuxmusl-arm64': 0.34.1 - '@img/sharp-linuxmusl-x64': 0.34.1 - '@img/sharp-wasm32': 0.34.1 - '@img/sharp-win32-ia32': 0.34.1 - '@img/sharp-win32-x64': 0.34.1 - - shiki@3.4.1: - dependencies: - '@shikijs/core': 3.4.1 - '@shikijs/engine-javascript': 3.4.1 - '@shikijs/engine-oniguruma': 3.4.1 - '@shikijs/langs': 3.4.1 - '@shikijs/themes': 3.4.1 - '@shikijs/types': 3.4.1 + '@img/sharp-linux-arm': 0.34.2 + '@img/sharp-linux-arm64': 0.34.2 + '@img/sharp-linux-s390x': 0.34.2 + '@img/sharp-linux-x64': 0.34.2 + '@img/sharp-linuxmusl-arm64': 0.34.2 + '@img/sharp-linuxmusl-x64': 0.34.2 + '@img/sharp-wasm32': 0.34.2 + '@img/sharp-win32-arm64': 0.34.2 + '@img/sharp-win32-ia32': 0.34.2 + '@img/sharp-win32-x64': 0.34.2 + + shiki@3.4.2: + dependencies: + '@shikijs/core': 3.4.2 + '@shikijs/engine-javascript': 3.4.2 + '@shikijs/engine-oniguruma': 3.4.2 + '@shikijs/langs': 3.4.2 + '@shikijs/themes': 3.4.2 + '@shikijs/types': 3.4.2 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -7858,7 +7869,7 @@ snapshots: trough@2.2.0: {} - tsconfck@3.1.5(typescript@5.8.3): + tsconfck@3.1.6(typescript@5.8.3): optionalDependencies: typescript: 5.8.3 @@ -7903,7 +7914,7 @@ snapshots: typesafe-path@0.2.2: {} - typescript-auto-import-cache@0.3.5: + typescript-auto-import-cache@0.3.6: dependencies: semver: 7.7.2 @@ -8058,91 +8069,91 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite-plugin-pwa@1.0.0(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(sass@1.88.0)(terser@5.39.2)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0): + vite-plugin-pwa@1.0.0(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0): dependencies: debug: 4.4.1 pretty-bytes: 6.1.1 tinyglobby: 0.2.13 - vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(sass@1.88.0)(terser@5.39.2)(yaml@2.8.0) + vite: 6.3.5(@types/node@22.15.21)(jiti@2.4.2)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0) workbox-build: 7.3.0(@types/babel__core@7.20.5) workbox-window: 7.3.0 transitivePeerDependencies: - supports-color - vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(sass@1.88.0)(terser@5.39.2)(yaml@2.8.0): + vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0): dependencies: esbuild: 0.25.4 fdir: 6.4.4(picomatch@4.0.2) picomatch: 4.0.2 postcss: 8.5.3 - rollup: 4.40.2 + rollup: 4.41.0 tinyglobby: 0.2.13 optionalDependencies: - '@types/node': 22.15.18 + '@types/node': 22.15.21 fsevents: 2.3.3 jiti: 2.4.2 - sass: 1.88.0 + sass: 1.89.0 terser: 5.39.2 yaml: 2.8.0 - vitefu@1.0.6(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(sass@1.88.0)(terser@5.39.2)(yaml@2.8.0)): + vitefu@1.0.6(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)): optionalDependencies: - vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(sass@1.88.0)(terser@5.39.2)(yaml@2.8.0) + vite: 6.3.5(@types/node@22.15.21)(jiti@2.4.2)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0) - volar-service-css@0.0.62(@volar/language-service@2.4.13): + volar-service-css@0.0.62(@volar/language-service@2.4.14): dependencies: vscode-css-languageservice: 6.3.5 vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.1.0 optionalDependencies: - '@volar/language-service': 2.4.13 + '@volar/language-service': 2.4.14 - volar-service-emmet@0.0.62(@volar/language-service@2.4.13): + volar-service-emmet@0.0.62(@volar/language-service@2.4.14): dependencies: '@emmetio/css-parser': 0.4.0 '@emmetio/html-matcher': 1.3.0 '@vscode/emmet-helper': 2.11.0 vscode-uri: 3.1.0 optionalDependencies: - '@volar/language-service': 2.4.13 + '@volar/language-service': 2.4.14 - volar-service-html@0.0.62(@volar/language-service@2.4.13): + volar-service-html@0.0.62(@volar/language-service@2.4.14): dependencies: vscode-html-languageservice: 5.4.0 vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.1.0 optionalDependencies: - '@volar/language-service': 2.4.13 + '@volar/language-service': 2.4.14 - volar-service-prettier@0.0.62(@volar/language-service@2.4.13): + volar-service-prettier@0.0.62(@volar/language-service@2.4.14): dependencies: vscode-uri: 3.1.0 optionalDependencies: - '@volar/language-service': 2.4.13 + '@volar/language-service': 2.4.14 - volar-service-typescript-twoslash-queries@0.0.62(@volar/language-service@2.4.13): + volar-service-typescript-twoslash-queries@0.0.62(@volar/language-service@2.4.14): dependencies: vscode-uri: 3.1.0 optionalDependencies: - '@volar/language-service': 2.4.13 + '@volar/language-service': 2.4.14 - volar-service-typescript@0.0.62(@volar/language-service@2.4.13): + volar-service-typescript@0.0.62(@volar/language-service@2.4.14): dependencies: path-browserify: 1.0.1 semver: 7.7.2 - typescript-auto-import-cache: 0.3.5 + typescript-auto-import-cache: 0.3.6 vscode-languageserver-textdocument: 1.0.12 vscode-nls: 5.2.0 vscode-uri: 3.1.0 optionalDependencies: - '@volar/language-service': 2.4.13 + '@volar/language-service': 2.4.14 - volar-service-yaml@0.0.62(@volar/language-service@2.4.13): + volar-service-yaml@0.0.62(@volar/language-service@2.4.14): dependencies: vscode-uri: 3.1.0 yaml-language-server: 1.15.0 optionalDependencies: - '@volar/language-service': 2.4.13 + '@volar/language-service': 2.4.14 vscode-css-languageservice@6.3.5: dependencies: @@ -8428,21 +8439,21 @@ snapshots: yocto-queue@1.2.1: {} - yocto-spinner@0.2.2: + yocto-spinner@0.2.3: dependencies: yoctocolors: 2.1.1 yoctocolors@2.1.1: {} - zod-to-json-schema@3.24.5(zod@3.24.4): + zod-to-json-schema@3.24.5(zod@3.25.17): dependencies: - zod: 3.24.4 + zod: 3.25.17 - zod-to-ts@1.2.0(typescript@5.8.3)(zod@3.24.4): + zod-to-ts@1.2.0(typescript@5.8.3)(zod@3.25.17): dependencies: typescript: 5.8.3 - zod: 3.24.4 + zod: 3.25.17 - zod@3.24.4: {} + zod@3.25.17: {} zwitch@2.0.4: {} diff --git a/plugin/lib/src/rust/api/luau.dart b/plugin/lib/src/rust/api/luau.dart index 57d323b6..78a66ed9 100644 --- a/plugin/lib/src/rust/api/luau.dart +++ b/plugin/lib/src/rust/api/luau.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.9.0. +// @generated by `flutter_rust_bridge`@ 2.10.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/plugin/lib/src/rust/api/plugin.dart b/plugin/lib/src/rust/api/plugin.dart index 672ccad5..a7eca546 100644 --- a/plugin/lib/src/rust/api/plugin.dart +++ b/plugin/lib/src/rust/api/plugin.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.9.0. +// @generated by `flutter_rust_bridge`@ 2.10.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/plugin/lib/src/rust/api/simple.dart b/plugin/lib/src/rust/api/simple.dart index 582f6241..4c8585e5 100644 --- a/plugin/lib/src/rust/api/simple.dart +++ b/plugin/lib/src/rust/api/simple.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.9.0. +// @generated by `flutter_rust_bridge`@ 2.10.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/plugin/lib/src/rust/frb_generated.dart b/plugin/lib/src/rust/frb_generated.dart index e6ea6034..5741c2c3 100644 --- a/plugin/lib/src/rust/frb_generated.dart +++ b/plugin/lib/src/rust/frb_generated.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.9.0. +// @generated by `flutter_rust_bridge`@ 2.10.0. // ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field @@ -65,7 +65,7 @@ class RustLib extends BaseEntrypoint { kDefaultExternalLibraryLoaderConfig; @override - String get codegenVersion => '2.9.0'; + String get codegenVersion => '2.10.0'; @override int get rustContentHash => 2139481266; diff --git a/plugin/lib/src/rust/frb_generated.io.dart b/plugin/lib/src/rust/frb_generated.io.dart index 0aa68c1d..25ea36c4 100644 --- a/plugin/lib/src/rust/frb_generated.io.dart +++ b/plugin/lib/src/rust/frb_generated.io.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.9.0. +// @generated by `flutter_rust_bridge`@ 2.10.0. // ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field diff --git a/plugin/lib/src/rust/frb_generated.web.dart b/plugin/lib/src/rust/frb_generated.web.dart index 958c21db..3e26cb76 100644 --- a/plugin/lib/src/rust/frb_generated.web.dart +++ b/plugin/lib/src/rust/frb_generated.web.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.9.0. +// @generated by `flutter_rust_bridge`@ 2.10.0. // ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field diff --git a/plugin/pubspec.lock b/plugin/pubspec.lock index d9d766f9..e75ebf0a 100644 --- a/plugin/pubspec.lock +++ b/plugin/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: analyzer - sha256: f4c21c94eb4623b183c1014a470196b3910701bea9b926e6c91270d756e6fc60 + sha256: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0" url: "https://pub.dev" source: hosted - version: "7.4.1" + version: "7.4.5" ansicolor: dependency: transitive description: @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: archive - sha256: a7f37ff061d7abc2fcf213554b9dcaca713c5853afa5c065c44888bc9ccaf813 + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" url: "https://pub.dev" source: hosted - version: "4.0.6" + version: "4.0.7" args: dependency: transitive description: @@ -206,10 +206,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" + sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.0" ffi: dependency: transitive description: @@ -222,10 +222,10 @@ packages: dependency: "direct dev" description: name: ffigen - sha256: "72d732c33557fc0ca9b46379d3deff2dadbdc539696dc0b270189e2989be20ef" + sha256: cb3edbfb68ac5283102a2deb7057913d3a1fb16552dacda0c07eb144497e4891 url: "https://pub.dev" source: hosted - version: "18.1.0" + version: "19.0.0" file: dependency: transitive description: @@ -246,10 +246,10 @@ packages: dependency: "direct main" description: name: flutter_rust_bridge - sha256: "5a5c7a5deeef2cc2ffe6076a33b0429f4a20ceac22a397297aed2b1eb067e611" + sha256: b416ff56002789e636244fb4cc449f587656eff995e5a7169457eb0593fcaddb url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.10.0" frontend_server_client: dependency: transitive description: @@ -278,10 +278,10 @@ packages: dependency: transitive description: name: http - sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" http_multi_server: dependency: transitive description: @@ -351,10 +351,10 @@ packages: dependency: "direct main" description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -511,10 +511,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.6" timing: dependency: transitive description: @@ -559,10 +559,10 @@ packages: dependency: transitive description: name: web_socket - sha256: bfe6f435f6ec49cb6c01da1e275ae4228719e59a6b067048c51e72d9d63bcc4b + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.1" web_socket_channel: dependency: transitive description: diff --git a/plugin/pubspec.yaml b/plugin/pubspec.yaml index a35629de..b5006fdd 100644 --- a/plugin/pubspec.yaml +++ b/plugin/pubspec.yaml @@ -5,7 +5,7 @@ publish_to: none environment: sdk: '>=3.3.0 <4.0.0' dependencies: - flutter_rust_bridge: 2.9.0 + flutter_rust_bridge: 2.10.0 collection: ^1.16.0 setonix_api: path: ../api @@ -19,4 +19,4 @@ dependencies: dev_dependencies: build_runner: ^2.4.14 dart_mappable_builder: ^4.3.1 - ffigen: ^18.0.0 + ffigen: ^19.0.0 diff --git a/plugin/rust/Cargo.lock b/plugin/rust/Cargo.lock index f88a88ce..43707683 100644 --- a/plugin/rust/Cargo.lock +++ b/plugin/rust/Cargo.lock @@ -93,9 +93,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "block-buffer" @@ -142,9 +142,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.22" +version = "1.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" +checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" dependencies = [ "shlex", ] @@ -243,9 +243,9 @@ dependencies = [ [[package]] name = "flutter_rust_bridge" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f8c0dee6249225e815dcff3f3a39b98d9f66fdb3c392a432715b646bfa4da02" +checksum = "ff1d2ad18166cead8c1b92b1c00e64aacc32e6ebd1ac95f77089c276c9c6bd8c" dependencies = [ "allo-isolate", "android_logger", @@ -272,9 +272,9 @@ dependencies = [ [[package]] name = "flutter_rust_bridge_macros" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e88d604908d9eccb4ca9c26640ce41033165cbef041460e704ae28bd5208bce" +checksum = "36cf75fba54902e67db5eef4a520df1c9f604db6f71f106fbc012477e2d81542" dependencies = [ "hex", "md-5", diff --git a/plugin/rust/Cargo.toml b/plugin/rust/Cargo.toml index d09c03b9..5c9c383c 100644 --- a/plugin/rust/Cargo.toml +++ b/plugin/rust/Cargo.toml @@ -8,7 +8,7 @@ crate-type = ["cdylib", "staticlib"] [dependencies] anyhow = { version = "1", features = ["backtrace"] } -flutter_rust_bridge = "=2.9.0" +flutter_rust_bridge = "=2.10.0" mlua = { version = "0.10.2", features = ["luau", "send", "async", "anyhow", "serialize"]} futures = "0.3" serde_json = "1.0" diff --git a/plugin/rust/src/frb_generated.rs b/plugin/rust/src/frb_generated.rs index 009f99ab..db7e7870 100644 --- a/plugin/rust/src/frb_generated.rs +++ b/plugin/rust/src/frb_generated.rs @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.9.0. +// @generated by `flutter_rust_bridge`@ 2.10.0. #![allow( non_camel_case_types, @@ -39,7 +39,7 @@ flutter_rust_bridge::frb_generated_boilerplate!( default_rust_opaque = RustOpaqueMoi, default_rust_auto_opaque = RustAutoOpaqueMoi, ); -pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.9.0"; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.10.0"; pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 2139481266; // Section: executor @@ -1194,7 +1194,7 @@ impl SseEncode for usize { #[cfg(not(target_family = "wasm"))] mod io { // This file is automatically generated, so please do not edit it. - // @generated by `flutter_rust_bridge`@ 2.9.0. + // @generated by `flutter_rust_bridge`@ 2.10.0. // Section: imports @@ -1247,7 +1247,7 @@ pub use io::*; #[cfg(target_family = "wasm")] mod web { // This file is automatically generated, so please do not edit it. - // @generated by `flutter_rust_bridge`@ 2.9.0. + // @generated by `flutter_rust_bridge`@ 2.10.0. // Section: imports diff --git a/server/pubspec.lock b/server/pubspec.lock index cb038411..775c2940 100644 --- a/server/pubspec.lock +++ b/server/pubspec.lock @@ -239,10 +239,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" + sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.0" ffi: dependency: transitive description: @@ -271,10 +271,10 @@ packages: dependency: transitive description: name: flutter_rust_bridge - sha256: "5a5c7a5deeef2cc2ffe6076a33b0429f4a20ceac22a397297aed2b1eb067e611" + sha256: b416ff56002789e636244fb4cc449f587656eff995e5a7169457eb0593fcaddb url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.10.0" frontend_server_client: dependency: transitive description: @@ -351,10 +351,10 @@ packages: dependency: "direct dev" description: name: lints - sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "6.0.0" logging: dependency: "direct main" description: @@ -396,14 +396,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" - native_assets_cli: - dependency: "direct main" - description: - name: native_assets_cli - sha256: "298ba641e47b44f039601141ab26f6865186b5d05b7f313d45797513d7205b3d" - url: "https://pub.dev" - source: hosted - version: "0.14.0" networker: dependency: "direct main" description: @@ -600,26 +592,26 @@ packages: dependency: "direct dev" description: name: test - sha256: f1665eeffe3b6b193548b5f515e8d1b54ccd9a6e0e7721a417e134e7ed7f06a1 + sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" url: "https://pub.dev" source: hosted - version: "1.26.0" + version: "1.26.2" test_api: dependency: transitive description: name: test_api - sha256: "6c7653816b1c938e121b69ff63a33c9dc68102b65a5fb0a5c0f9786256ed33e6" + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.5" + version: "0.7.6" test_core: dependency: transitive description: name: test_core - sha256: "3caa7c3956b366643b2dedecff764cc32030317b2a15252aed845570df6bcc0f" + sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" url: "https://pub.dev" source: hosted - version: "0.6.9" + version: "0.6.11" timing: dependency: transitive description: @@ -701,4 +693,4 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.7.0 <4.0.0" + dart: ">=3.8.0-0 <4.0.0" diff --git a/server/pubspec.yaml b/server/pubspec.yaml index 3f894bec..2237f4a8 100644 --- a/server/pubspec.yaml +++ b/server/pubspec.yaml @@ -32,9 +32,8 @@ dependencies: path: ../plugin bloc_concurrency: ^0.3.0 dart_mappable: ^4.2.2 - native_assets_cli: ^0.14.0 dev_dependencies: - lints: ^5.0.0 + lints: ^6.0.0 test: ^1.24.0 dart_mappable_builder: ^4.2.3 build_runner: ^2.4.12 diff --git a/tools/pubspec.lock b/tools/pubspec.lock index 022b26e2..6191549c 100644 --- a/tools/pubspec.lock +++ b/tools/pubspec.lock @@ -102,10 +102,10 @@ packages: dependency: "direct main" description: name: lints - sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "6.0.0" meta: dependency: transitive description: @@ -251,4 +251,4 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.7.0 <4.0.0" + dart: ">=3.8.0-0 <4.0.0" diff --git a/tools/pubspec.yaml b/tools/pubspec.yaml index f524a5ee..0a751303 100644 --- a/tools/pubspec.yaml +++ b/tools/pubspec.yaml @@ -4,7 +4,7 @@ environment: dependencies: archive: ^4.0.1 args: ^2.5.0 - lints: ^5.0.0 + lints: ^6.0.0 shelf: ^1.4.1 shelf_proxy: ^1.0.4 shelf_static: ^1.1.2 From 06bc79c140b7d39f9f657354d9783b639b4a8ce4 Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Tue, 10 Jun 2025 14:20:05 +0200 Subject: [PATCH 03/18] Add importing and exporting accounts --- api/lib/src/models/data.dart | 27 +++++++++++ api/lib/src/models/meta.dart | 15 ++++++ api/lib/src/models/meta.mapper.dart | 4 ++ app/lib/api/open.dart | 22 +++++---- app/lib/l10n/app_en.arb | 6 ++- app/lib/main.dart | 3 ++ app/lib/pages/settings/accounts.dart | 66 ++++++++++++++++++++++---- app/lib/services/file_system.dart | 70 ++++++++++++++++++++++++++-- 8 files changed, 189 insertions(+), 24 deletions(-) diff --git a/api/lib/src/models/data.dart b/api/lib/src/models/data.dart index 6e82693e..03bdaa70 100644 --- a/api/lib/src/models/data.dart +++ b/api/lib/src/models/data.dart @@ -22,6 +22,7 @@ const kPackTranslationsPath = 'translations'; const kPackBackgroundsPath = 'backgrounds'; const kPackScriptsPath = 'scripts'; const kPackModesPath = 'modes'; +const kPackAccountsPath = 'accounts'; const kGameTablePath = 'tables'; const kGameTeamPath = 'teams.json'; @@ -312,6 +313,32 @@ class SetonixData extends ArchiveData { if (mode == null) return null; return MapEntry(e, mode); }).nonNulls); + + SetonixData addAccount(SetonixAccount setonixAccount) { + final accountId = setonixAccount.name; + return setAsset( + '$kPackAccountsPath/$accountId.key', setonixAccount.privateKey) + .setAsset( + '$kPackAccountsPath/$accountId.pub', setonixAccount.publicKey); + } + + Iterable getAccounts() sync* { + const kKeySuffix = '.key'; + final privateKeys = getAssets('$kPackAccountsPath/', true) + .where((e) => e.endsWith(kKeySuffix)); + for (final path in privateKeys) { + final name = path.substring(0, path.length - kKeySuffix.length); + final privateKey = getAsset(path); + if (privateKey == null) continue; + final publicKey = getAsset('$kPackAccountsPath/$name.pub'); + if (publicKey == null) continue; + yield SetonixAccount( + privateKey: privateKey, + publicKey: publicKey, + name: name, + ); + } + } } class SetonixFile { diff --git a/api/lib/src/models/meta.dart b/api/lib/src/models/meta.dart index 4ef78d23..b9ffbb21 100644 --- a/api/lib/src/models/meta.dart +++ b/api/lib/src/models/meta.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:dart_mappable/dart_mappable.dart'; part 'meta.mapper.dart'; @@ -9,6 +11,7 @@ enum FileType { pack, game, template, + accounts, } @MappableClass() @@ -65,3 +68,15 @@ final class DataMetadata with DataMetadataMappable { return serversLastUsed.values.fold(addedAt, (a, b) => a.isAfter(b) ? a : b); } } + +final class SetonixAccount { + final Uint8List privateKey; + final Uint8List publicKey; + final String name; + + SetonixAccount({ + required this.privateKey, + required this.publicKey, + required this.name, + }); +} diff --git a/api/lib/src/models/meta.mapper.dart b/api/lib/src/models/meta.mapper.dart index b2f71bf8..78540fc0 100644 --- a/api/lib/src/models/meta.mapper.dart +++ b/api/lib/src/models/meta.mapper.dart @@ -31,6 +31,8 @@ class FileTypeMapper extends EnumMapper { return FileType.game; case r'template': return FileType.template; + case r'accounts': + return FileType.accounts; default: throw MapperException.unknownEnumValue(value); } @@ -45,6 +47,8 @@ class FileTypeMapper extends EnumMapper { return r'game'; case FileType.template: return r'template'; + case FileType.accounts: + return r'accounts'; } } } diff --git a/app/lib/api/open.dart b/app/lib/api/open.dart index 8d45eeb9..591fe19d 100644 --- a/app/lib/api/open.dart +++ b/app/lib/api/open.dart @@ -93,29 +93,31 @@ Future importFileData(BuildContext context, SetonixFileSystem fileSystem, final data = file.load(); final metadata = data.getMetadataOrDefault(); final type = metadata.type; + final loc = AppLocalizations.of(context); final result = await showDialog( context: context, builder: (context) => AlertDialog( title: Text(switch (type) { - FileType.pack => AppLocalizations.of(context).importPack, - FileType.game => AppLocalizations.of(context).importGame, - FileType.template => AppLocalizations.of(context).importTemplate, + FileType.pack => loc.importPack, + FileType.game => loc.importGame, + FileType.template => loc.importTemplate, + FileType.accounts => loc.importAccounts, }), content: Text(switch (type) { - FileType.pack => AppLocalizations.of(context).importPackDescription, - FileType.game => AppLocalizations.of(context).importGameDescription, - FileType.template => - AppLocalizations.of(context).importTemplateDescription, + FileType.pack => loc.importPackDescription, + FileType.game => loc.importGameDescription, + FileType.template => loc.importTemplateDescription, + FileType.accounts => loc.importAccountsDescription, }), actions: [ TextButton.icon( onPressed: () => Navigator.of(context).pop(false), - label: Text(AppLocalizations.of(context).cancel), + label: Text(loc.cancel), icon: Icon(PhosphorIconsLight.prohibit), ), ElevatedButton.icon( onPressed: () => Navigator.of(context).pop(true), - label: Text(AppLocalizations.of(context).import), + label: Text(loc.import), icon: Icon(PhosphorIconsLight.boxArrowDown), ), ], @@ -130,5 +132,7 @@ Future importFileData(BuildContext context, SetonixFileSystem fileSystem, await fileSystem.templateSystem.createFile(metadata.name, data); case FileType.game: await fileSystem.worldSystem.createFile(metadata.name, data); + case FileType.accounts: + await fileSystem.importAccountsFromData(data); } } diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index c6442c70..64536c62 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -252,5 +252,9 @@ "comfortable": "Comfortable", "stackedCards": "Stacked cards", "accounts": "Accounts", - "create": "Create" + "create": "Create", + "backupKey": "Backup key", + "importAccounts": "Import accounts", + "importAccountsDescription": "Are you sure you want to import the accounts?", + "backupAllKeys": "Backup all keys" } diff --git a/app/lib/main.dart b/app/lib/main.dart index 78193a6d..941d18df 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -4,7 +4,10 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; +import 'package:setonix/pages/settings/data.dart'; +import 'package:setonix/pages/settings/general.dart'; import 'package:setonix/pages/settings/input.dart'; +import 'package:setonix/pages/settings/personalization.dart'; import 'package:setonix/src/generated/i18n/app_localizations.dart'; import 'package:flutter_web_plugins/url_strategy.dart'; import 'package:material_leap/material_leap.dart'; diff --git a/app/lib/pages/settings/accounts.dart b/app/lib/pages/settings/accounts.dart index 44e51675..c93da122 100644 --- a/app/lib/pages/settings/accounts.dart +++ b/app/lib/pages/settings/accounts.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:lw_file_system/lw_file_system.dart'; +import 'package:setonix/api/open.dart'; +import 'package:setonix/api/save.dart'; import 'package:setonix/src/generated/i18n/app_localizations.dart'; import 'package:material_leap/material_leap.dart'; import 'package:phosphor_flutter/phosphor_flutter.dart'; @@ -42,6 +44,30 @@ class _AccountsSettingsPageState extends State { inView: widget.inView, backgroundColor: widget.inView ? Colors.transparent : null, title: Text(AppLocalizations.of(context).accounts), + actions: [ + IconButton( + icon: const PhosphorIcon(PhosphorIconsLight.arrowSquareIn), + tooltip: AppLocalizations.of(context).import, + onPressed: () async { + await importFile( + context, + _fileSystem, + ); + setState(() { + _buildKeysFuture(); + }); + }, + ), + IconButton( + icon: const PhosphorIcon(PhosphorIconsLight.export), + tooltip: AppLocalizations.of(context).backupAllKeys, + onPressed: () async { + final data = await _fileSystem.exportAccounts(); + if (!context.mounted) return; + exportData(context, data, 'accounts'); + }, + ), + ], ), body: FutureBuilder>( future: _keysFuture, @@ -51,18 +77,40 @@ class _AccountsSettingsPageState extends State { itemCount: keys.length, itemBuilder: (context, index) { final key = keys[index]; + void deleteKey() { + _privateKeyFileSystem.deleteFile(key); + _publicKeyFileSystem.deleteFile(key); + setState(() { + keys.removeAt(index); + }); + } + return Dismissible( key: Key(key), - child: ListTile( - title: Text(key.substring(1)), + child: ContextRegion( + builder: (context, button, controller) => ListTile( + title: Text(key.substring(1)), + trailing: button, + ), + menuChildren: [ + MenuItemButton( + leadingIcon: + const PhosphorIcon(PhosphorIconsLight.export), + onPressed: () async { + final data = await _fileSystem.exportAccounts([key]); + if (!context.mounted) return; + exportData(context, data, key); + }, + child: Text(AppLocalizations.of(context).backupKey), + ), + MenuItemButton( + leadingIcon: const PhosphorIcon(PhosphorIconsLight.trash), + onPressed: deleteKey, + child: Text(AppLocalizations.of(context).delete), + ), + ], ), - onDismissed: (direction) { - _privateKeyFileSystem.deleteFile(key); - _publicKeyFileSystem.deleteFile(key); - setState(() { - keys.removeAt(index); - }); - }, + onDismissed: (direction) => deleteKey(), ); }, ); diff --git a/app/lib/services/file_system.dart b/app/lib/services/file_system.dart index b41ed774..f432259f 100644 --- a/app/lib/services/file_system.dart +++ b/app/lib/services/file_system.dart @@ -43,7 +43,7 @@ class SetonixFileSystem { event.database.createObjectStore('packs-data'); } if (event.oldVersion < 3) { - event.database.createObjectStore('keys'); + event.database.createObjectStore('accounts'); } } @@ -125,9 +125,9 @@ class SetonixFileSystem { privateKeySystem = KeyFileSystem.fromPlatform( FileSystemConfig( passwordStorage: SecureStoragePasswordStorage(), - storeName: 'keys', + storeName: 'accounts', getDirectory: (storage) async => - '${await getSetonixDirectory()}/Keys', + '${await getSetonixDirectory()}/Accounts', database: 'setonix.db', databaseVersion: kDatabaseVersion, keySuffix: '.key', @@ -137,9 +137,9 @@ class SetonixFileSystem { publicKeySystem = KeyFileSystem.fromPlatform( FileSystemConfig( passwordStorage: SecureStoragePasswordStorage(), - storeName: 'keys', + storeName: 'accounts', getDirectory: (storage) async => - '${await getSetonixDirectory()}/Keys', + '${await getSetonixDirectory()}/Accounts', database: 'setonix.db', databaseVersion: kDatabaseVersion, keySuffix: '.pub', @@ -237,4 +237,64 @@ class SetonixFileSystem { await publicKeySystem .createFileWithName(Uint8List.fromList(publicKey.bytes), name: name); } + + Future getAccount(String name) async { + final privateKey = await privateKeySystem.getFile(name); + if (privateKey == null) return null; + final publicKey = await publicKeySystem.getFile(name); + if (publicKey == null) return null; + return SetonixAccount( + privateKey: privateKey, + publicKey: publicKey, + name: name, + ); + } + + Future deleteAccount(String name) async { + await privateKeySystem.deleteFile(name); + await publicKeySystem.deleteFile(name); + } + + Future importAccountsFromData(SetonixData data) => + importAccounts(data.getAccounts().toList()); + + Future importAccounts(List accounts) async { + for (final account in accounts) { + final name = await privateKeySystem.createFileWithName( + account.privateKey, + name: account.name, + ); + await publicKeySystem.updateFile( + name, + account.publicKey, + ); + } + } + + Future exportAccounts( + [List? names, List? accounts]) async { + var data = SetonixData.empty().setMetadata(FileMetadata( + type: FileType.accounts, + )); + names ??= await privateKeySystem.getKeys(); + final allAccounts = accounts ?? + (await Future.wait( + names.map((name) => getAccount(name)), + )) + .whereType() + .toList(); + for (final account in allAccounts) { + final privateKey = account.privateKey; + final publicKey = account.publicKey; + if (privateKey.isEmpty || publicKey.isEmpty) continue; + data = data.addAccount( + SetonixAccount( + privateKey: privateKey, + publicKey: publicKey, + name: account.name, + ), + ); + } + return data; + } } From 9752d5a16fe110fa06a704168789776176848247 Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Thu, 12 Jun 2025 22:38:41 +0200 Subject: [PATCH 04/18] Add auth events, add fingerprints to account --- api/lib/src/event/client.dart | 9 + api/lib/src/event/event.mapper.dart | 261 ++++++++++++++++++++++++++ api/lib/src/event/process/client.dart | 2 + api/lib/src/event/process/server.dart | 4 + api/lib/src/event/server.dart | 9 + api/lib/src/event/state.dart | 3 + api/lib/src/event/state.mapper.dart | 17 ++ app/lib/helpers/crypto.dart | 19 ++ app/lib/pages/settings/accounts.dart | 16 +- app/lib/services/file_system.dart | 9 + 10 files changed, 344 insertions(+), 5 deletions(-) create mode 100644 app/lib/helpers/crypto.dart diff --git a/api/lib/src/event/client.dart b/api/lib/src/event/client.dart index 051c7171..4f30e89f 100644 --- a/api/lib/src/event/client.dart +++ b/api/lib/src/event/client.dart @@ -157,3 +157,12 @@ final class ModeChangeRequest extends ClientWorldEvent ModeChangeRequest(this.location); ModeChangeRequest.plain() : location = null; } + +@MappableClass() +final class AuthenticateRequest extends ClientWorldEvent + with AuthenticateRequestMappable { + final Uint8List signature; + final Uint8List publicKey; + + AuthenticateRequest(this.signature, this.publicKey); +} diff --git a/api/lib/src/event/event.mapper.dart b/api/lib/src/event/event.mapper.dart index 75873eec..6f96a337 100644 --- a/api/lib/src/event/event.mapper.dart +++ b/api/lib/src/event/event.mapper.dart @@ -135,6 +135,7 @@ class ServerWorldEventMapper extends SubClassMapperBase { DialogsClosedMapper.ensureInitialized(); ImagesUpdatedMapper.ensureInitialized(); ServerStateUpdatedMapper.ensureInitialized(); + AuthenticatedRequestedMapper.ensureInitialized(); HybridWorldEventMapper.ensureInitialized(); } return _instance!; @@ -1774,6 +1775,138 @@ class _ServerStateUpdatedCopyWithImpl<$R, $Out> _ServerStateUpdatedCopyWithImpl<$R2, $Out2>($value, $cast, t); } +class AuthenticatedRequestedMapper + extends SubClassMapperBase { + AuthenticatedRequestedMapper._(); + + static AuthenticatedRequestedMapper? _instance; + static AuthenticatedRequestedMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = AuthenticatedRequestedMapper._()); + ServerWorldEventMapper.ensureInitialized().addSubMapper(_instance!); + } + return _instance!; + } + + @override + final String id = 'AuthenticatedRequested'; + + static Uint8List _$challenge(AuthenticatedRequested v) => v.challenge; + static const Field _f$challenge = + Field('challenge', _$challenge); + static bool _$isRequired(AuthenticatedRequested v) => v.isRequired; + static const Field _f$isRequired = + Field('isRequired', _$isRequired, opt: true, def: true); + + @override + final MappableFields fields = const { + #challenge: _f$challenge, + #isRequired: _f$isRequired, + }; + + @override + final String discriminatorKey = 'type'; + @override + final dynamic discriminatorValue = 'AuthenticatedRequested'; + @override + late final ClassMapperBase superMapper = + ServerWorldEventMapper.ensureInitialized(); + + static AuthenticatedRequested _instantiate(DecodingData data) { + return AuthenticatedRequested(data.dec(_f$challenge), + isRequired: data.dec(_f$isRequired)); + } + + @override + final Function instantiate = _instantiate; + + static AuthenticatedRequested fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static AuthenticatedRequested fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin AuthenticatedRequestedMappable { + String toJson() { + return AuthenticatedRequestedMapper.ensureInitialized() + .encodeJson(this as AuthenticatedRequested); + } + + Map toMap() { + return AuthenticatedRequestedMapper.ensureInitialized() + .encodeMap(this as AuthenticatedRequested); + } + + AuthenticatedRequestedCopyWith + get copyWith => _AuthenticatedRequestedCopyWithImpl< + AuthenticatedRequested, AuthenticatedRequested>( + this as AuthenticatedRequested, $identity, $identity); + @override + String toString() { + return AuthenticatedRequestedMapper.ensureInitialized() + .stringifyValue(this as AuthenticatedRequested); + } + + @override + bool operator ==(Object other) { + return AuthenticatedRequestedMapper.ensureInitialized() + .equalsValue(this as AuthenticatedRequested, other); + } + + @override + int get hashCode { + return AuthenticatedRequestedMapper.ensureInitialized() + .hashValue(this as AuthenticatedRequested); + } +} + +extension AuthenticatedRequestedValueCopy<$R, $Out> + on ObjectCopyWith<$R, AuthenticatedRequested, $Out> { + AuthenticatedRequestedCopyWith<$R, AuthenticatedRequested, $Out> + get $asAuthenticatedRequested => $base.as((v, t, t2) => + _AuthenticatedRequestedCopyWithImpl<$R, $Out>(v, t, t2)); +} + +abstract class AuthenticatedRequestedCopyWith< + $R, + $In extends AuthenticatedRequested, + $Out> implements ServerWorldEventCopyWith<$R, $In, $Out> { + @override + $R call({Uint8List? challenge, bool? isRequired}); + AuthenticatedRequestedCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t); +} + +class _AuthenticatedRequestedCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, AuthenticatedRequested, $Out> + implements + AuthenticatedRequestedCopyWith<$R, AuthenticatedRequested, $Out> { + _AuthenticatedRequestedCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + AuthenticatedRequestedMapper.ensureInitialized(); + @override + $R call({Uint8List? challenge, bool? isRequired}) => + $apply(FieldCopyWithData({ + if (challenge != null) #challenge: challenge, + if (isRequired != null) #isRequired: isRequired + })); + @override + AuthenticatedRequested $make(CopyWithData data) => + AuthenticatedRequested(data.get(#challenge, or: $value.challenge), + isRequired: data.get(#isRequired, or: $value.isRequired)); + + @override + AuthenticatedRequestedCopyWith<$R2, AuthenticatedRequested, $Out2> + $chain<$R2, $Out2>(Then<$Out2, $R2> t) => + _AuthenticatedRequestedCopyWithImpl<$R2, $Out2>($value, $cast, t); +} + class ClientWorldEventMapper extends SubClassMapperBase { ClientWorldEventMapper._(); @@ -1794,6 +1927,7 @@ class ClientWorldEventMapper extends SubClassMapperBase { DialogCloseRequestMapper.ensureInitialized(); ImagesRequestMapper.ensureInitialized(); ModeChangeRequestMapper.ensureInitialized(); + AuthenticateRequestMapper.ensureInitialized(); HybridWorldEventMapper.ensureInitialized(); } return _instance!; @@ -3368,6 +3502,133 @@ class _ModeChangeRequestCopyWithImpl<$R, $Out> _ModeChangeRequestCopyWithImpl<$R2, $Out2>($value, $cast, t); } +class AuthenticateRequestMapper + extends SubClassMapperBase { + AuthenticateRequestMapper._(); + + static AuthenticateRequestMapper? _instance; + static AuthenticateRequestMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = AuthenticateRequestMapper._()); + ClientWorldEventMapper.ensureInitialized().addSubMapper(_instance!); + } + return _instance!; + } + + @override + final String id = 'AuthenticateRequest'; + + static Uint8List _$signature(AuthenticateRequest v) => v.signature; + static const Field _f$signature = + Field('signature', _$signature); + static Uint8List _$publicKey(AuthenticateRequest v) => v.publicKey; + static const Field _f$publicKey = + Field('publicKey', _$publicKey); + + @override + final MappableFields fields = const { + #signature: _f$signature, + #publicKey: _f$publicKey, + }; + + @override + final String discriminatorKey = 'type'; + @override + final dynamic discriminatorValue = 'AuthenticateRequest'; + @override + late final ClassMapperBase superMapper = + ClientWorldEventMapper.ensureInitialized(); + + static AuthenticateRequest _instantiate(DecodingData data) { + return AuthenticateRequest(data.dec(_f$signature), data.dec(_f$publicKey)); + } + + @override + final Function instantiate = _instantiate; + + static AuthenticateRequest fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static AuthenticateRequest fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin AuthenticateRequestMappable { + String toJson() { + return AuthenticateRequestMapper.ensureInitialized() + .encodeJson(this as AuthenticateRequest); + } + + Map toMap() { + return AuthenticateRequestMapper.ensureInitialized() + .encodeMap(this as AuthenticateRequest); + } + + AuthenticateRequestCopyWith get copyWith => _AuthenticateRequestCopyWithImpl< + AuthenticateRequest, AuthenticateRequest>( + this as AuthenticateRequest, $identity, $identity); + @override + String toString() { + return AuthenticateRequestMapper.ensureInitialized() + .stringifyValue(this as AuthenticateRequest); + } + + @override + bool operator ==(Object other) { + return AuthenticateRequestMapper.ensureInitialized() + .equalsValue(this as AuthenticateRequest, other); + } + + @override + int get hashCode { + return AuthenticateRequestMapper.ensureInitialized() + .hashValue(this as AuthenticateRequest); + } +} + +extension AuthenticateRequestValueCopy<$R, $Out> + on ObjectCopyWith<$R, AuthenticateRequest, $Out> { + AuthenticateRequestCopyWith<$R, AuthenticateRequest, $Out> + get $asAuthenticateRequest => $base.as( + (v, t, t2) => _AuthenticateRequestCopyWithImpl<$R, $Out>(v, t, t2)); +} + +abstract class AuthenticateRequestCopyWith<$R, $In extends AuthenticateRequest, + $Out> implements ClientWorldEventCopyWith<$R, $In, $Out> { + @override + $R call({Uint8List? signature, Uint8List? publicKey}); + AuthenticateRequestCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t); +} + +class _AuthenticateRequestCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, AuthenticateRequest, $Out> + implements AuthenticateRequestCopyWith<$R, AuthenticateRequest, $Out> { + _AuthenticateRequestCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + AuthenticateRequestMapper.ensureInitialized(); + @override + $R call({Uint8List? signature, Uint8List? publicKey}) => + $apply(FieldCopyWithData({ + if (signature != null) #signature: signature, + if (publicKey != null) #publicKey: publicKey + })); + @override + AuthenticateRequest $make(CopyWithData data) => AuthenticateRequest( + data.get(#signature, or: $value.signature), + data.get(#publicKey, or: $value.publicKey)); + + @override + AuthenticateRequestCopyWith<$R2, AuthenticateRequest, $Out2> + $chain<$R2, $Out2>(Then<$Out2, $R2> t) => + _AuthenticateRequestCopyWithImpl<$R2, $Out2>($value, $cast, t); +} + class HybridWorldEventMapper extends SubClassMapperBase { HybridWorldEventMapper._(); diff --git a/api/lib/src/event/process/client.dart b/api/lib/src/event/process/client.dart index 281ae172..374ea4b6 100644 --- a/api/lib/src/event/process/client.dart +++ b/api/lib/src/event/process/client.dart @@ -279,5 +279,7 @@ ServerResponse? processClientEvent( : assetManager.getPack(location.namespace)?.getMode(location.id); return ServerResponse.builder( WorldInitialized.fromMode(mode, state), channel); + case AuthenticateRequest(): + return null; } } diff --git a/api/lib/src/event/process/server.dart b/api/lib/src/event/process/server.dart index 884afbf7..e4f0d2f9 100644 --- a/api/lib/src/event/process/server.dart +++ b/api/lib/src/event/process/server.dart @@ -349,5 +349,9 @@ ServerProcessed processServerEvent( return ServerProcessed(state.copyWith( serverState: event.state, )); + case AuthenticatedRequested(): + return ServerProcessed(state.copyWith( + authRequest: event, + )); } } diff --git a/api/lib/src/event/server.dart b/api/lib/src/event/server.dart index 2a1f3134..32c6f42e 100644 --- a/api/lib/src/event/server.dart +++ b/api/lib/src/event/server.dart @@ -224,3 +224,12 @@ final class ServerStateUpdated extends ServerWorldEvent const ServerStateUpdated(this.state); } + +@MappableClass() +final class AuthenticatedRequested extends ServerWorldEvent + with AuthenticatedRequestedMappable { + final Uint8List challenge; + final bool isRequired; + + const AuthenticatedRequested(this.challenge, {this.isRequired = true}); +} diff --git a/api/lib/src/event/state.dart b/api/lib/src/event/state.dart index 6783edd9..42a6d822 100644 --- a/api/lib/src/event/state.dart +++ b/api/lib/src/event/state.dart @@ -11,6 +11,7 @@ import '../models/meta.dart'; import '../models/server.dart'; import '../models/table.dart'; import '../models/vector.dart'; +import 'event.dart'; part 'state.mapper.dart'; @@ -34,6 +35,7 @@ final class WorldState with WorldStateMappable { final List dialogs; final Map images; final ServerState serverState; + final AuthenticatedRequested? authRequest; const WorldState({ this.name, @@ -47,6 +49,7 @@ final class WorldState with WorldStateMappable { this.dialogs = const [], this.images = const {}, this.serverState = const ServerState(), + this.authRequest, required this.data, }); diff --git a/api/lib/src/event/state.mapper.dart b/api/lib/src/event/state.mapper.dart index 65c4266b..b9e6f09b 100644 --- a/api/lib/src/event/state.mapper.dart +++ b/api/lib/src/event/state.mapper.dart @@ -65,6 +65,7 @@ class WorldStateMapper extends ClassMapperBase { ChatMessageMapper.ensureInitialized(); GameDialogMapper.ensureInitialized(); ServerStateMapper.ensureInitialized(); + AuthenticatedRequestedMapper.ensureInitialized(); } return _instance!; } @@ -105,6 +106,9 @@ class WorldStateMapper extends ClassMapperBase { static ServerState _$serverState(WorldState v) => v.serverState; static const Field _f$serverState = Field('serverState', _$serverState, opt: true, def: const ServerState()); + static AuthenticatedRequested? _$authRequest(WorldState v) => v.authRequest; + static const Field _f$authRequest = + Field('authRequest', _$authRequest, opt: true); static SetonixData _$data(WorldState v) => v.data; static const Field _f$data = Field('data', _$data); @@ -121,6 +125,7 @@ class WorldStateMapper extends ClassMapperBase { #dialogs: _f$dialogs, #images: _f$images, #serverState: _f$serverState, + #authRequest: _f$authRequest, #data: _f$data, }; @@ -137,6 +142,7 @@ class WorldStateMapper extends ClassMapperBase { dialogs: data.dec(_f$dialogs), images: data.dec(_f$images), serverState: data.dec(_f$serverState), + authRequest: data.dec(_f$authRequest), data: data.dec(_f$data)); } @@ -204,6 +210,8 @@ abstract class WorldStateCopyWith<$R, $In extends WorldState, $Out> MapCopyWith<$R, String, Uint8List, ObjectCopyWith<$R, Uint8List, Uint8List>> get images; ServerStateCopyWith<$R, ServerState, ServerState> get serverState; + AuthenticatedRequestedCopyWith<$R, AuthenticatedRequested, + AuthenticatedRequested>? get authRequest; $R call( {String? name, GameTable? table, @@ -216,6 +224,7 @@ abstract class WorldStateCopyWith<$R, $In extends WorldState, $Out> List? dialogs, Map? images, ServerState? serverState, + AuthenticatedRequested? authRequest, SetonixData? data}); WorldStateCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } @@ -260,6 +269,11 @@ class _WorldStateCopyWithImpl<$R, $Out> ServerStateCopyWith<$R, ServerState, ServerState> get serverState => $value.serverState.copyWith.$chain((v) => call(serverState: v)); @override + AuthenticatedRequestedCopyWith<$R, AuthenticatedRequested, + AuthenticatedRequested>? + get authRequest => + $value.authRequest?.copyWith.$chain((v) => call(authRequest: v)); + @override $R call( {Object? name = $none, GameTable? table, @@ -272,6 +286,7 @@ class _WorldStateCopyWithImpl<$R, $Out> List? dialogs, Map? images, ServerState? serverState, + Object? authRequest = $none, SetonixData? data}) => $apply(FieldCopyWithData({ if (name != $none) #name: name, @@ -285,6 +300,7 @@ class _WorldStateCopyWithImpl<$R, $Out> if (dialogs != null) #dialogs: dialogs, if (images != null) #images: images, if (serverState != null) #serverState: serverState, + if (authRequest != $none) #authRequest: authRequest, if (data != null) #data: data })); @override @@ -300,6 +316,7 @@ class _WorldStateCopyWithImpl<$R, $Out> dialogs: data.get(#dialogs, or: $value.dialogs), images: data.get(#images, or: $value.images), serverState: data.get(#serverState, or: $value.serverState), + authRequest: data.get(#authRequest, or: $value.authRequest), data: data.get(#data, or: $value.data)); @override diff --git a/app/lib/helpers/crypto.dart b/app/lib/helpers/crypto.dart new file mode 100644 index 00000000..51c8fd64 --- /dev/null +++ b/app/lib/helpers/crypto.dart @@ -0,0 +1,19 @@ +import 'dart:typed_data'; + +import 'package:convert/convert.dart'; +import 'package:cryptography_plus/cryptography_plus.dart'; + +Future generateFingerprint(Uint8List publicKeyBytes, + [bool short = false]) async { + final digest = await Sha256().hash(publicKeyBytes); + var hexString = hex.encode(digest.bytes); + if (short) { + hexString = hexString.substring(0, 32); + } + final output = hexString.replaceAllMapped( + RegExp(r'.{2}'), + (match) => '${match.group(0)!}:', + ); + + return output.endsWith(':') ? output.substring(0, output.length - 1) : output; +} diff --git a/app/lib/pages/settings/accounts.dart b/app/lib/pages/settings/accounts.dart index c93da122..ace2ae1a 100644 --- a/app/lib/pages/settings/accounts.dart +++ b/app/lib/pages/settings/accounts.dart @@ -20,7 +20,7 @@ class AccountsSettingsPage extends StatefulWidget { class _AccountsSettingsPageState extends State { late final KeyFileSystem _privateKeyFileSystem, _publicKeyFileSystem; - Future>? _keysFuture; + Future>? _keysFuture; late final SetonixFileSystem _fileSystem; @override @@ -33,7 +33,12 @@ class _AccountsSettingsPageState extends State { } void _buildKeysFuture() { - _keysFuture = _privateKeyFileSystem.getKeys(); + _keysFuture = _privateKeyFileSystem + .getKeys() + .then((e) => Future.wait(e.map((key) async { + final fingerprint = await _fileSystem.getFingerprint(key, true); + return (key, fingerprint); + }))); } @override @@ -69,14 +74,14 @@ class _AccountsSettingsPageState extends State { ), ], ), - body: FutureBuilder>( + body: FutureBuilder>( future: _keysFuture, builder: (context, state) { - final keys = state.data ?? []; + final keys = state.data ?? <(String, String)>[]; return ListView.builder( itemCount: keys.length, itemBuilder: (context, index) { - final key = keys[index]; + final (key, fingerprint) = keys[index]; void deleteKey() { _privateKeyFileSystem.deleteFile(key); _publicKeyFileSystem.deleteFile(key); @@ -90,6 +95,7 @@ class _AccountsSettingsPageState extends State { child: ContextRegion( builder: (context, button, controller) => ListTile( title: Text(key.substring(1)), + subtitle: Text(fingerprint), trailing: button, ), menuChildren: [ diff --git a/app/lib/services/file_system.dart b/app/lib/services/file_system.dart index f432259f..237d0746 100644 --- a/app/lib/services/file_system.dart +++ b/app/lib/services/file_system.dart @@ -9,6 +9,7 @@ import 'package:idb_shim/idb.dart'; import 'package:lw_file_system/lw_file_system.dart'; import 'package:setonix/api/open.dart'; import 'package:setonix/api/storage.dart'; +import 'package:setonix/helpers/crypto.dart'; import 'package:setonix_api/setonix_api.dart'; const imageTypeGroup = fs.XTypeGroup( @@ -297,4 +298,12 @@ class SetonixFileSystem { } return data; } + + Future getFingerprint(String key, [bool short = false]) async { + final publicKey = await publicKeySystem.getFile(key); + if (publicKey == null) { + return ''; + } + return generateFingerprint(publicKey, short); + } } From c696ae0d86b9bb103cf0a0cbe8303f0a5f5bc21b Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Fri, 13 Jun 2025 19:02:17 +0200 Subject: [PATCH 05/18] Add game state --- api/lib/src/event/process/server.dart | 1 + api/lib/src/event/state.dart | 8 ++++ api/lib/src/event/state.mapper.dart | 56 ++++++++++++++++++++++++++ app/lib/api/settings.dart | 20 +++++++--- app/lib/bloc/world/bloc.dart | 2 + app/lib/l10n/app_en.arb | 6 ++- app/lib/pages/game/auth.dart | 46 +++++++++++++++++++++ app/lib/pages/game/page.dart | 57 ++++++++++++++++++++------- 8 files changed, 174 insertions(+), 22 deletions(-) create mode 100644 app/lib/pages/game/auth.dart diff --git a/api/lib/src/event/process/server.dart b/api/lib/src/event/process/server.dart index 390591c1..0f5bd634 100644 --- a/api/lib/src/event/process/server.dart +++ b/api/lib/src/event/process/server.dart @@ -103,6 +103,7 @@ ServerProcessed processServerEvent( info: event.info ?? state.info, dialogs: event.clearUserInterface ? [] : state.dialogs, images: event.clearUserInterface ? {} : state.images, + gameState: GameState.play, )); case TeamJoined(): return ServerProcessed(state.copyWith( diff --git a/api/lib/src/event/state.dart b/api/lib/src/event/state.dart index 6783edd9..f5e57b53 100644 --- a/api/lib/src/event/state.dart +++ b/api/lib/src/event/state.dart @@ -20,6 +20,12 @@ enum WorldOperationMode { boards, } +@MappableEnum() +enum GameState { + configuration, + play, +} + @MappableClass() final class WorldState with WorldStateMappable { final GameTable table; @@ -34,9 +40,11 @@ final class WorldState with WorldStateMappable { final List dialogs; final Map images; final ServerState serverState; + final GameState gameState; const WorldState({ this.name, + this.gameState = GameState.play, this.table = const GameTable(), this.tableName = '', this.info = const GameInfo(), diff --git a/api/lib/src/event/state.mapper.dart b/api/lib/src/event/state.mapper.dart index 65c4266b..39b844cc 100644 --- a/api/lib/src/event/state.mapper.dart +++ b/api/lib/src/event/state.mapper.dart @@ -52,6 +52,52 @@ extension WorldOperationModeMapperExtension on WorldOperationMode { } } +class GameStateMapper extends EnumMapper { + GameStateMapper._(); + + static GameStateMapper? _instance; + static GameStateMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = GameStateMapper._()); + } + return _instance!; + } + + static GameState fromValue(dynamic value) { + ensureInitialized(); + return MapperContainer.globals.fromValue(value); + } + + @override + GameState decode(dynamic value) { + switch (value) { + case r'configuration': + return GameState.configuration; + case r'play': + return GameState.play; + default: + throw MapperException.unknownEnumValue(value); + } + } + + @override + dynamic encode(GameState self) { + switch (self) { + case GameState.configuration: + return r'configuration'; + case GameState.play: + return r'play'; + } + } +} + +extension GameStateMapperExtension on GameState { + String toValue() { + GameStateMapper.ensureInitialized(); + return MapperContainer.globals.toValue(this) as String; + } +} + class WorldStateMapper extends ClassMapperBase { WorldStateMapper._(); @@ -59,6 +105,7 @@ class WorldStateMapper extends ClassMapperBase { static WorldStateMapper ensureInitialized() { if (_instance == null) { MapperContainer.globals.use(_instance = WorldStateMapper._()); + GameStateMapper.ensureInitialized(); GameTableMapper.ensureInitialized(); GameInfoMapper.ensureInitialized(); FileMetadataMapper.ensureInitialized(); @@ -75,6 +122,9 @@ class WorldStateMapper extends ClassMapperBase { static String? _$name(WorldState v) => v.name; static const Field _f$name = Field('name', _$name, opt: true); + static GameState _$gameState(WorldState v) => v.gameState; + static const Field _f$gameState = + Field('gameState', _$gameState, opt: true, def: GameState.play); static GameTable _$table(WorldState v) => v.table; static const Field _f$table = Field('table', _$table, opt: true, def: const GameTable()); @@ -111,6 +161,7 @@ class WorldStateMapper extends ClassMapperBase { @override final MappableFields fields = const { #name: _f$name, + #gameState: _f$gameState, #table: _f$table, #tableName: _f$tableName, #info: _f$info, @@ -127,6 +178,7 @@ class WorldStateMapper extends ClassMapperBase { static WorldState _instantiate(DecodingData data) { return WorldState( name: data.dec(_f$name), + gameState: data.dec(_f$gameState), table: data.dec(_f$table), tableName: data.dec(_f$tableName), info: data.dec(_f$info), @@ -206,6 +258,7 @@ abstract class WorldStateCopyWith<$R, $In extends WorldState, $Out> ServerStateCopyWith<$R, ServerState, ServerState> get serverState; $R call( {String? name, + GameState? gameState, GameTable? table, String? tableName, GameInfo? info, @@ -262,6 +315,7 @@ class _WorldStateCopyWithImpl<$R, $Out> @override $R call( {Object? name = $none, + GameState? gameState, GameTable? table, String? tableName, GameInfo? info, @@ -275,6 +329,7 @@ class _WorldStateCopyWithImpl<$R, $Out> SetonixData? data}) => $apply(FieldCopyWithData({ if (name != $none) #name: name, + if (gameState != null) #gameState: gameState, if (table != null) #table: table, if (tableName != null) #tableName: tableName, if (info != null) #info: info, @@ -290,6 +345,7 @@ class _WorldStateCopyWithImpl<$R, $Out> @override WorldState $make(CopyWithData data) => WorldState( name: data.get(#name, or: $value.name), + gameState: data.get(#gameState, or: $value.gameState), table: data.get(#table, or: $value.table), tableName: data.get(#tableName, or: $value.tableName), info: data.get(#info, or: $value.info), diff --git a/app/lib/api/settings.dart b/app/lib/api/settings.dart index e6e5454d..02120016 100644 --- a/app/lib/api/settings.dart +++ b/app/lib/api/settings.dart @@ -4,18 +4,26 @@ import 'package:flutter/material.dart'; import '../pages/settings/home.dart'; -Future openSettings(BuildContext context) => showGeneralDialog( +Future openSettings( + BuildContext context, { + SettingsView view = SettingsView.general, +}) => + showGeneralDialog( context: context, pageBuilder: (context, animation, secondaryAnimation) => ScaffoldMessenger( child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), child: Dialog( - clipBehavior: Clip.antiAlias, - child: ConstrainedBox( - constraints: - const BoxConstraints(maxHeight: 800, maxWidth: 1000), - child: const SettingsPage(isDialog: true))), + clipBehavior: Clip.antiAlias, + child: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 800, maxWidth: 1000), + child: SettingsPage( + isDialog: true, + view: view, + ), + ), + ), ), ), barrierDismissible: true, diff --git a/app/lib/bloc/world/bloc.dart b/app/lib/bloc/world/bloc.dart index 4db97c8b..ffbbf08b 100644 --- a/app/lib/bloc/world/bloc.dart +++ b/app/lib/bloc/world/bloc.dart @@ -50,6 +50,7 @@ class WorldBloc extends Bloc { String? name, SetonixData? data, GameTable? table, + GameState gameState = GameState.play, }) : super(ClientWorldState( assetManager: GameAssetManager( fileSystem: fileSystem, @@ -62,6 +63,7 @@ class WorldBloc extends Bloc { table: table ?? data?.getTable() ?? const GameTable(), metadata: data?.getMetadata() ?? const FileMetadata(), info: data?.getInfo() ?? const GameInfo(), + gameState: gameState, ), )) { pluginSystem = PluginSystem(server: _WorldServerInterfaceImpl(this)); diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index a71bea40..f39f5462 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -251,5 +251,9 @@ "standard": "Standard", "comfortable": "Comfortable", "accounts": "Accounts", - "create": "Create" + "create": "Create", + "stackedCards": "Stacked cards", + "configuringGame": "Configuring game...", + "authenticate": "Authenticate", + "authenticateDescription": "The server requested authentication. Select the account you want to use to authenticate." } diff --git a/app/lib/pages/game/auth.dart b/app/lib/pages/game/auth.dart new file mode 100644 index 00000000..d89de847 --- /dev/null +++ b/app/lib/pages/game/auth.dart @@ -0,0 +1,46 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:material_leap/material_leap.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; +import 'package:setonix/api/settings.dart'; +import 'package:setonix/pages/settings/home.dart'; +import 'package:setonix/src/generated/i18n/app_localizations.dart'; + +class AuthGameView extends StatelessWidget { + const AuthGameView({super.key}); + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), + child: Container( + color: Colors.black.withValues(alpha: 0.5), + ), + ), + ResponsiveAlertDialog( + title: Text(AppLocalizations.of(context).authenticate), + constraints: BoxConstraints( + maxWidth: LeapBreakpoints.medium, + ), + headerActions: [ + IconButton( + icon: const Icon(PhosphorIconsLight.gear), + onPressed: () { + openSettings(context, view: SettingsView.accounts); + }, + ), + ], + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(AppLocalizations.of(context).authenticateDescription), + ], + ), + ) + ], + ); + } +} diff --git a/app/lib/pages/game/page.dart b/app/lib/pages/game/page.dart index 9da267b0..61577999 100644 --- a/app/lib/pages/game/page.dart +++ b/app/lib/pages/game/page.dart @@ -2,6 +2,7 @@ import 'package:flame/game.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:setonix/api/open.dart'; +import 'package:setonix/pages/game/auth.dart'; import 'package:setonix/pages/game/dialog.dart'; import 'package:setonix/src/generated/i18n/app_localizations.dart'; import 'package:material_leap/material_leap.dart'; @@ -65,6 +66,7 @@ class _GamePageState extends State { name: widget.name, data: data, colorScheme: Theme.of(context).colorScheme, + gameState: address == null ? GameState.play : GameState.configuration, ); await world.state.assetManager.loadPacks(); if (address != null) { @@ -183,7 +185,7 @@ class _GamePageState extends State { DrawerView.notes => const GameNotesDrawer(), }, ), - body: BlocListener( + body: BlocConsumer( listenWhen: (previous, current) => previous.messages.length != current.messages.length, listener: (context, state) { @@ -224,20 +226,45 @@ class _GamePageState extends State { ), ); }, - child: GameWidget( - game: BoardGame( - bloc: context.read(), - settingsCubit: context.read(), - contextMenuController: _contextMenuController, - onEscape: () => Scaffold.of(context).openDrawer(), - ), - focusNode: _focusNode, - initialActiveOverlays: ['dialogs', 'filter'], - overlayBuilderMap: { - 'dialogs': (context, game) => GameDialogOverlay(), - 'filter': (context, game) => GameFilterView(), - }, - ), + buildWhen: (previous, current) => + previous.world.gameState != current.world.gameState, + builder: (context, state) { + return Stack(children: [ + if (state.world.gameState == + GameState.configuration) + Center( + child: Column( + children: [ + CircularProgressIndicator(), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context) + .configuringGame, + style: + Theme.of(context).textTheme.bodyMedium, + ), + ], + )) + else + GameWidget( + game: BoardGame( + bloc: context.read(), + settingsCubit: context.read(), + contextMenuController: _contextMenuController, + onEscape: () => + Scaffold.of(context).openDrawer(), + ), + focusNode: _focusNode, + initialActiveOverlays: ['dialogs', 'filter'], + overlayBuilderMap: { + 'dialogs': (context, game) => + GameDialogOverlay(), + 'filter': (context, game) => GameFilterView(), + }, + ), + AuthGameView(), + ]); + }, ), ); }, From 1575b4c0bbfc59020af1bc131e3474e8deb2e6ef Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Fri, 13 Jun 2025 19:32:55 +0200 Subject: [PATCH 06/18] Add auth request ui --- api/lib/helpers.dart | 1 + api/lib/setonix_api.dart | 1 + {app/lib => api/lib/src}/helpers/crypto.dart | 7 +- api/lib/src/models/meta.dart | 4 + api/pubspec.lock | 2 +- api/pubspec.yaml | 1 + app/lib/bloc/world/bloc.dart | 20 +++ app/lib/l10n/app_en.arb | 4 +- app/lib/pages/game/auth.dart | 143 +++++++++++++++---- app/lib/pages/settings/accounts.dart | 22 ++- app/lib/services/file_system.dart | 16 +-- 11 files changed, 166 insertions(+), 55 deletions(-) create mode 100644 api/lib/helpers.dart rename {app/lib => api/lib/src}/helpers/crypto.dart (63%) diff --git a/api/lib/helpers.dart b/api/lib/helpers.dart new file mode 100644 index 00000000..165c8c85 --- /dev/null +++ b/api/lib/helpers.dart @@ -0,0 +1 @@ +export 'src/helpers/crypto.dart'; diff --git a/api/lib/setonix_api.dart b/api/lib/setonix_api.dart index c4f2287c..9f87654b 100644 --- a/api/lib/setonix_api.dart +++ b/api/lib/setonix_api.dart @@ -1,6 +1,7 @@ /// The Linwood Setonix API library; +export 'helpers.dart'; export 'models.dart'; export 'event.dart'; export 'services.dart'; diff --git a/app/lib/helpers/crypto.dart b/api/lib/src/helpers/crypto.dart similarity index 63% rename from app/lib/helpers/crypto.dart rename to api/lib/src/helpers/crypto.dart index 51c8fd64..a1063d21 100644 --- a/app/lib/helpers/crypto.dart +++ b/api/lib/src/helpers/crypto.dart @@ -1,11 +1,10 @@ import 'dart:typed_data'; import 'package:convert/convert.dart'; -import 'package:cryptography_plus/cryptography_plus.dart'; +import 'package:crypto/crypto.dart'; -Future generateFingerprint(Uint8List publicKeyBytes, - [bool short = false]) async { - final digest = await Sha256().hash(publicKeyBytes); +String generateFingerprint(Uint8List publicKeyBytes, [bool short = false]) { + final digest = sha256.convert(publicKeyBytes); var hexString = hex.encode(digest.bytes); if (short) { hexString = hexString.substring(0, 32); diff --git a/api/lib/src/models/meta.dart b/api/lib/src/models/meta.dart index b9ffbb21..942d0a52 100644 --- a/api/lib/src/models/meta.dart +++ b/api/lib/src/models/meta.dart @@ -1,6 +1,7 @@ import 'dart:typed_data'; import 'package:dart_mappable/dart_mappable.dart'; +import 'package:setonix_api/src/helpers/crypto.dart'; part 'meta.mapper.dart'; @@ -79,4 +80,7 @@ final class SetonixAccount { required this.publicKey, required this.name, }); + + String getFingerprint([bool short = false]) => + generateFingerprint(publicKey, short); } diff --git a/api/pubspec.lock b/api/pubspec.lock index db6ef648..eb387ce7 100644 --- a/api/pubspec.lock +++ b/api/pubspec.lock @@ -146,7 +146,7 @@ packages: source: hosted version: "1.19.1" convert: - dependency: transitive + dependency: "direct main" description: name: convert sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 diff --git a/api/pubspec.yaml b/api/pubspec.yaml index a8702958..90c8cc0c 100644 --- a/api/pubspec.yaml +++ b/api/pubspec.yaml @@ -28,6 +28,7 @@ dependencies: ref: b7787191b0705ff0a22149409b1b360468d9e06d crypto: ^3.0.5 collection: ^1.18.0 + convert: ^3.1.2 # path: ^1.8.0 dev_dependencies: diff --git a/app/lib/bloc/world/bloc.dart b/app/lib/bloc/world/bloc.dart index ffbbf08b..81c96e05 100644 --- a/app/lib/bloc/world/bloc.dart +++ b/app/lib/bloc/world/bloc.dart @@ -1,4 +1,5 @@ import 'package:bloc_concurrency/bloc_concurrency.dart'; +import 'package:cryptography_plus/cryptography_plus.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart' show ColorScheme; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -201,4 +202,23 @@ class WorldBloc extends Bloc { // ignore: empty_catches } catch (e) {} } + + Future authenticate(SetonixAccount account) async { + final challenge = state.world.authRequest?.challenge; + if (challenge == null) { + return; + } + final generator = Ed25519(); + final keyPair = SimpleKeyPairData( + account.privateKey, + publicKey: SimplePublicKey(account.publicKey, type: KeyPairType.ed25519), + type: KeyPairType.ed25519, + ); + final signature = await generator.sign(challenge, keyPair: keyPair); + final request = AuthenticateRequest( + Uint8List.fromList(signature.bytes), + account.publicKey, + ); + process(request); + } } diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index f8cb0c44..64974429 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -259,5 +259,7 @@ "backupKey": "Backup key", "importAccounts": "Import accounts", "importAccountsDescription": "Are you sure you want to import the accounts?", - "backupAllKeys": "Backup all keys" + "backupAllKeys": "Backup all keys", + "noAccount": "There are no accounts available", + "authenticateRequired": "Authentication is required to connect to the server" } diff --git a/app/lib/pages/game/auth.dart b/app/lib/pages/game/auth.dart index d89de847..0fd70dcf 100644 --- a/app/lib/pages/game/auth.dart +++ b/app/lib/pages/game/auth.dart @@ -1,46 +1,131 @@ import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:material_leap/material_leap.dart'; import 'package:phosphor_flutter/phosphor_flutter.dart'; import 'package:setonix/api/settings.dart'; +import 'package:setonix/bloc/world/bloc.dart'; +import 'package:setonix/bloc/world/state.dart'; import 'package:setonix/pages/settings/home.dart'; +import 'package:setonix/services/file_system.dart'; import 'package:setonix/src/generated/i18n/app_localizations.dart'; +import 'package:setonix_api/setonix_api.dart'; -class AuthGameView extends StatelessWidget { +class AuthGameView extends StatefulWidget { const AuthGameView({super.key}); + @override + State createState() => _AuthGameViewState(); +} + +class _AuthGameViewState extends State { + late final SetonixFileSystem _fileSystem; + Future>? _keysFuture; + + @override + void initState() { + super.initState(); + _fileSystem = context.read(); + _buildKeysFuture(); + } + + void _buildKeysFuture() { + _keysFuture = _fileSystem.getAccounts(); + } + @override Widget build(BuildContext context) { - return Stack( - children: [ - BackdropFilter( - filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), - child: Container( - color: Colors.black.withValues(alpha: 0.5), - ), - ), - ResponsiveAlertDialog( - title: Text(AppLocalizations.of(context).authenticate), - constraints: BoxConstraints( - maxWidth: LeapBreakpoints.medium, - ), - headerActions: [ - IconButton( - icon: const Icon(PhosphorIconsLight.gear), - onPressed: () { - openSettings(context, view: SettingsView.accounts); - }, - ), - ], - content: Column( - mainAxisSize: MainAxisSize.min, + return BlocBuilder( + buildWhen: (previous, current) => + previous.world.authRequest != current.world.authRequest, + builder: (context, state) { + final authRequest = state.world.authRequest; + if (authRequest == null) { + return const SizedBox.shrink(); + } + return Stack( children: [ - Text(AppLocalizations.of(context).authenticateDescription), + BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), + child: Container( + color: Colors.black.withValues(alpha: 0.5), + ), + ), + ResponsiveAlertDialog( + title: Text(AppLocalizations.of(context).authenticate), + constraints: BoxConstraints( + maxWidth: LeapBreakpoints.medium, + ), + headerActions: [ + IconButton( + icon: const Icon(PhosphorIconsLight.gear), + onPressed: () async { + await openSettings(context, view: SettingsView.accounts); + setState(() { + _buildKeysFuture(); + }); + }, + ), + ], + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(AppLocalizations.of(context).authenticateDescription), + if (authRequest.isRequired) + Text( + AppLocalizations.of(context).authenticateRequired, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Colors.red, + ), + ), + Flexible( + child: FutureBuilder>( + future: _keysFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == + ConnectionState.waiting) { + return const Center( + child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center( + child: Text( + AppLocalizations.of(context).error, + ), + ); + } else if (!snapshot.hasData || + snapshot.data!.isEmpty) { + return Center( + child: + Text(AppLocalizations.of(context).noAccount), + ); + } else { + final accounts = snapshot.data!; + return ListView.builder( + shrinkWrap: true, + itemCount: accounts.length, + itemBuilder: (context, index) { + final account = accounts[index]; + return ListTile( + title: Text(account.name), + subtitle: Text(account.getFingerprint(true)), + onTap: () { + context + .read() + .authenticate(account); + }, + ); + }, + ); + } + }, + ), + ), + ], + ), + ) ], - ), - ) - ], - ); + ); + }); } } diff --git a/app/lib/pages/settings/accounts.dart b/app/lib/pages/settings/accounts.dart index ace2ae1a..7fb40038 100644 --- a/app/lib/pages/settings/accounts.dart +++ b/app/lib/pages/settings/accounts.dart @@ -7,6 +7,7 @@ import 'package:setonix/src/generated/i18n/app_localizations.dart'; import 'package:material_leap/material_leap.dart'; import 'package:phosphor_flutter/phosphor_flutter.dart'; import 'package:setonix/services/file_system.dart'; +import 'package:setonix_api/setonix_api.dart'; import '../../bloc/settings.dart'; @@ -20,7 +21,7 @@ class AccountsSettingsPage extends StatefulWidget { class _AccountsSettingsPageState extends State { late final KeyFileSystem _privateKeyFileSystem, _publicKeyFileSystem; - Future>? _keysFuture; + Future>? _keysFuture; late final SetonixFileSystem _fileSystem; @override @@ -33,12 +34,7 @@ class _AccountsSettingsPageState extends State { } void _buildKeysFuture() { - _keysFuture = _privateKeyFileSystem - .getKeys() - .then((e) => Future.wait(e.map((key) async { - final fingerprint = await _fileSystem.getFingerprint(key, true); - return (key, fingerprint); - }))); + _keysFuture = _fileSystem.getAccounts(); } @override @@ -74,19 +70,21 @@ class _AccountsSettingsPageState extends State { ), ], ), - body: FutureBuilder>( + body: FutureBuilder>( future: _keysFuture, builder: (context, state) { - final keys = state.data ?? <(String, String)>[]; + final accounts = state.data ?? []; return ListView.builder( - itemCount: keys.length, + itemCount: accounts.length, itemBuilder: (context, index) { - final (key, fingerprint) = keys[index]; + final account = accounts[index]; + final key = account.name; + final fingerprint = account.getFingerprint(true); void deleteKey() { _privateKeyFileSystem.deleteFile(key); _publicKeyFileSystem.deleteFile(key); setState(() { - keys.removeAt(index); + accounts.removeAt(index); }); } diff --git a/app/lib/services/file_system.dart b/app/lib/services/file_system.dart index 237d0746..f42a882b 100644 --- a/app/lib/services/file_system.dart +++ b/app/lib/services/file_system.dart @@ -9,7 +9,6 @@ import 'package:idb_shim/idb.dart'; import 'package:lw_file_system/lw_file_system.dart'; import 'package:setonix/api/open.dart'; import 'package:setonix/api/storage.dart'; -import 'package:setonix/helpers/crypto.dart'; import 'package:setonix_api/setonix_api.dart'; const imageTypeGroup = fs.XTypeGroup( @@ -272,18 +271,19 @@ class SetonixFileSystem { } } + Future> getAccounts([List? names]) async { + names ??= await privateKeySystem.getKeys(); + return Future.wait( + names.map((name) => getAccount(name)), + ).then((accounts) => accounts.nonNulls.toList()); + } + Future exportAccounts( [List? names, List? accounts]) async { var data = SetonixData.empty().setMetadata(FileMetadata( type: FileType.accounts, )); - names ??= await privateKeySystem.getKeys(); - final allAccounts = accounts ?? - (await Future.wait( - names.map((name) => getAccount(name)), - )) - .whereType() - .toList(); + final allAccounts = accounts ?? await getAccounts(names); for (final account in allAccounts) { final privateKey = account.privateKey; final publicKey = account.publicKey; From 18f676ed0389fcbc881a545a87f569a6bcaa68ff Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Sat, 14 Jun 2025 12:23:43 +0200 Subject: [PATCH 07/18] Add user and challenge manager --- api/lib/services.dart | 2 + api/lib/src/event/client.dart | 19 ++++ api/lib/src/event/event.dart | 1 + api/lib/src/event/process/client.dart | 57 ++++++++---- api/lib/src/models/config.dart | 10 +++ api/lib/src/models/config.mapper.dart | 19 ++-- api/lib/src/models/meta.dart | 7 ++ api/lib/src/services/challenge.dart | 25 ++++++ api/lib/src/services/user.dart | 65 ++++++++++++++ api/lib/src/services/user.mapper.dart | 119 ++++++++++++++++++++++++++ api/pubspec.lock | 8 ++ api/pubspec.yaml | 1 + app/lib/bloc/multiplayer.dart | 18 +++- app/lib/bloc/world/bloc.dart | 36 +++----- app/lib/pages/game/auth.dart | 10 ++- server/lib/src/asset.mapper.dart | 10 +-- server/lib/src/bloc.dart | 29 +++++-- server/lib/src/config.dart | 3 + server/lib/src/server.dart | 23 ++++- server/pubspec.lock | 8 ++ 20 files changed, 401 insertions(+), 69 deletions(-) create mode 100644 api/lib/src/services/challenge.dart create mode 100644 api/lib/src/services/user.dart create mode 100644 api/lib/src/services/user.mapper.dart diff --git a/api/lib/services.dart b/api/lib/services.dart index 6b73810f..469c9a86 100644 --- a/api/lib/services.dart +++ b/api/lib/services.dart @@ -1,2 +1,4 @@ export 'src/services/asset.dart'; +export 'src/services/challenge.dart'; export 'src/services/network.dart'; +export 'src/services/user.dart'; diff --git a/api/lib/src/event/client.dart b/api/lib/src/event/client.dart index 4f30e89f..eeb9f889 100644 --- a/api/lib/src/event/client.dart +++ b/api/lib/src/event/client.dart @@ -164,5 +164,24 @@ final class AuthenticateRequest extends ClientWorldEvent final Uint8List signature; final Uint8List publicKey; + static final _generator = Ed25519(); + AuthenticateRequest(this.signature, this.publicKey); + + static Future build( + AuthenticatedRequested request, SetonixAccount account) async { + final challenge = request.challenge; + final keyPair = account.keyPair; + final signature = await _generator.sign(challenge, keyPair: keyPair); + return AuthenticateRequest( + Uint8List.fromList(signature.bytes), + account.publicKey, + ); + } + + Future verify(Uint8List challenge) => _generator.verify( + challenge, + signature: Signature(signature, + publicKey: SimplePublicKey(publicKey, type: KeyPairType.ed25519)), + ); } diff --git a/api/lib/src/event/event.dart b/api/lib/src/event/event.dart index fc9a45d5..fff4ce7a 100644 --- a/api/lib/src/event/event.dart +++ b/api/lib/src/event/event.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:cryptography_plus/cryptography_plus.dart'; import 'package:dart_mappable/dart_mappable.dart'; import 'package:networker/networker.dart'; import 'package:setonix_api/event.dart'; diff --git a/api/lib/src/event/process/client.dart b/api/lib/src/event/process/client.dart index 374ea4b6..ecee4007 100644 --- a/api/lib/src/event/process/client.dart +++ b/api/lib/src/event/process/client.dart @@ -9,6 +9,7 @@ bool isValidClientEvent( Channel channel, WorldState state, { required AssetManager assetManager, + ChallengeManager? challengeManager, }) => switch (event) { TeamJoinRequest() => state.info.teams.containsKey(event.team), @@ -117,22 +118,38 @@ Set _hybridNeedsUpdate(HybridWorldEvent event, WorldState state) => _ => {}, }; -ServerResponse? processClientEvent( - WorldEvent? event, Channel channel, WorldState state, - {required AssetManager assetManager, bool allowServerEvents = false}) { +Future processClientEvent( + WorldEvent? event, + Channel channel, + WorldState state, { + required AssetManager assetManager, + bool allowServerEvents = false, + ChallengeManager? challengeManager, + UserManager? userManager, +}) async { + buildInitialize() => WorldInitialized( + table: state.protectTable(channel), + info: state.info, + id: channel, + packsSignature: assetManager + .createSignature(state.info.packs.toSet()) + .values + .toList(), + teamMembers: state.teamMembers, + ); + if (event == null) { - return ServerResponse.builder( - WorldInitialized( - table: state.protectTable(channel), - info: state.info, - id: channel, - packsSignature: assetManager - .createSignature(state.info.packs.toSet()) - .values - .toList(), - teamMembers: state.teamMembers, - ), - channel); + if (challengeManager != null) { + final challenge = challengeManager.getChallenge(channel); + return ServerResponse.builder( + AuthenticatedRequested( + challenge, + isRequired: true, + ), + channel); + } + userManager?.addUser(channel); + return ServerResponse.builder(buildInitialize(), channel); } if (!isValidClientEvent(event, channel, state, assetManager: assetManager)) { return null; @@ -280,6 +297,14 @@ ServerResponse? processClientEvent( return ServerResponse.builder( WorldInitialized.fromMode(mode, state), channel); case AuthenticateRequest(): - return null; + final challenge = challengeManager?.getChallenge(channel); + if (challenge == null) return null; + final verified = await event.verify(challenge); + if (!verified) { + return ServerResponse.builder( + AuthenticatedRequested(challenge, isRequired: true), channel); + } + userManager?.addUser(channel, event.publicKey); + return ServerResponse.builder(buildInitialize(), channel); } } diff --git a/api/lib/src/models/config.dart b/api/lib/src/models/config.dart index cbfda0ff..b2a08e4b 100644 --- a/api/lib/src/models/config.dart +++ b/api/lib/src/models/config.dart @@ -26,6 +26,9 @@ final class SetonixConfig with SetonixConfigMappable { final String? description; static const String defaultDescription = 'A server for Setonix.'; static const String envDescription = 'SETONIX_DESCRIPTION'; + final String? guestPrefix; + static const String defaultGuestPrefix = 'Guest '; + static const String envGuestPrefix = 'SETONIX_GUEST_PREFIX'; const SetonixConfig({ this.host, @@ -35,6 +38,7 @@ final class SetonixConfig with SetonixConfigMappable { this.multiWorld, this.maxPlayers, this.description, + this.guestPrefix, }); static const defaultConfig = SetonixConfig( @@ -45,6 +49,7 @@ final class SetonixConfig with SetonixConfigMappable { multiWorld: defaultMultiWorld, maxPlayers: defaultMaxPlayers, description: defaultDescription, + guestPrefix: defaultGuestPrefix, ); static SetonixConfig fromEnvironment() { @@ -71,6 +76,10 @@ final class SetonixConfig with SetonixConfigMappable { ? String.fromEnvironment(envDescription, defaultValue: defaultDescription) : null, + guestPrefix: bool.hasEnvironment(envGuestPrefix) + ? String.fromEnvironment(envGuestPrefix, + defaultValue: defaultGuestPrefix) + : null, ); } @@ -82,5 +91,6 @@ final class SetonixConfig with SetonixConfigMappable { multiWorld: other.multiWorld ?? multiWorld, maxPlayers: other.maxPlayers ?? maxPlayers, description: other.description ?? description, + guestPrefix: other.guestPrefix ?? guestPrefix, ); } diff --git a/api/lib/src/models/config.mapper.dart b/api/lib/src/models/config.mapper.dart index c7e10576..567d94f4 100644 --- a/api/lib/src/models/config.mapper.dart +++ b/api/lib/src/models/config.mapper.dart @@ -41,6 +41,9 @@ class SetonixConfigMapper extends ClassMapperBase { static String? _$description(SetonixConfig v) => v.description; static const Field _f$description = Field('description', _$description, opt: true); + static String? _$guestPrefix(SetonixConfig v) => v.guestPrefix; + static const Field _f$guestPrefix = + Field('guestPrefix', _$guestPrefix, opt: true); @override final MappableFields fields = const { @@ -51,6 +54,7 @@ class SetonixConfigMapper extends ClassMapperBase { #multiWorld: _f$multiWorld, #maxPlayers: _f$maxPlayers, #description: _f$description, + #guestPrefix: _f$guestPrefix, }; static SetonixConfig _instantiate(DecodingData data) { @@ -61,7 +65,8 @@ class SetonixConfigMapper extends ClassMapperBase { autosave: data.dec(_f$autosave), multiWorld: data.dec(_f$multiWorld), maxPlayers: data.dec(_f$maxPlayers), - description: data.dec(_f$description)); + description: data.dec(_f$description), + guestPrefix: data.dec(_f$guestPrefix)); } @override @@ -124,7 +129,8 @@ abstract class SetonixConfigCopyWith<$R, $In extends SetonixConfig, $Out> bool? autosave, bool? multiWorld, int? maxPlayers, - String? description}); + String? description, + String? guestPrefix}); SetonixConfigCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } @@ -144,7 +150,8 @@ class _SetonixConfigCopyWithImpl<$R, $Out> Object? autosave = $none, Object? multiWorld = $none, Object? maxPlayers = $none, - Object? description = $none}) => + Object? description = $none, + Object? guestPrefix = $none}) => $apply(FieldCopyWithData({ if (host != $none) #host: host, if (port != $none) #port: port, @@ -152,7 +159,8 @@ class _SetonixConfigCopyWithImpl<$R, $Out> if (autosave != $none) #autosave: autosave, if (multiWorld != $none) #multiWorld: multiWorld, if (maxPlayers != $none) #maxPlayers: maxPlayers, - if (description != $none) #description: description + if (description != $none) #description: description, + if (guestPrefix != $none) #guestPrefix: guestPrefix })); @override SetonixConfig $make(CopyWithData data) => SetonixConfig( @@ -162,7 +170,8 @@ class _SetonixConfigCopyWithImpl<$R, $Out> autosave: data.get(#autosave, or: $value.autosave), multiWorld: data.get(#multiWorld, or: $value.multiWorld), maxPlayers: data.get(#maxPlayers, or: $value.maxPlayers), - description: data.get(#description, or: $value.description)); + description: data.get(#description, or: $value.description), + guestPrefix: data.get(#guestPrefix, or: $value.guestPrefix)); @override SetonixConfigCopyWith<$R2, SetonixConfig, $Out2> $chain<$R2, $Out2>( diff --git a/api/lib/src/models/meta.dart b/api/lib/src/models/meta.dart index 942d0a52..1e701d4d 100644 --- a/api/lib/src/models/meta.dart +++ b/api/lib/src/models/meta.dart @@ -1,5 +1,6 @@ import 'dart:typed_data'; +import 'package:cryptography_plus/cryptography_plus.dart'; import 'package:dart_mappable/dart_mappable.dart'; import 'package:setonix_api/src/helpers/crypto.dart'; @@ -81,6 +82,12 @@ final class SetonixAccount { required this.name, }); + KeyPair get keyPair => SimpleKeyPairData( + privateKey, + publicKey: SimplePublicKey(publicKey, type: KeyPairType.ed25519), + type: KeyPairType.ed25519, + ); + String getFingerprint([bool short = false]) => generateFingerprint(publicKey, short); } diff --git a/api/lib/src/services/challenge.dart b/api/lib/src/services/challenge.dart new file mode 100644 index 00000000..9fc790be --- /dev/null +++ b/api/lib/src/services/challenge.dart @@ -0,0 +1,25 @@ +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:networker/networker.dart'; + +class ChallengeManager { + final Map _challenges = {}; + + Uint8List getChallenge(Channel channel) { + return _challenges[channel] ??= generateChallenge(); + } + + void removeChallenge(Channel channel) { + _challenges.remove(channel); + } +} + +Uint8List generateChallenge() { + final random = Random.secure(); + final bytes = Uint8List(32); + for (var i = 0; i < bytes.length; i++) { + bytes[i] = random.nextInt(256); + } + return bytes; +} diff --git a/api/lib/src/services/user.dart b/api/lib/src/services/user.dart new file mode 100644 index 00000000..e7cd3017 --- /dev/null +++ b/api/lib/src/services/user.dart @@ -0,0 +1,65 @@ +import 'dart:typed_data'; + +import 'package:dart_mappable/dart_mappable.dart'; +import 'package:networker/networker.dart'; + +import '../models/config.dart'; + +part 'user.mapper.dart'; + +@MappableClass() +final class SetonixUser with SetonixUserMappable { + final Uint8List? publicKey; + final String name; + + const SetonixUser({ + this.publicKey, + required this.name, + }); +} + +final class UserManager { + final Map _users = {}; + final String guestPrefix; + int _nextGuestId = 1; + + UserManager([this.guestPrefix = SetonixConfig.defaultGuestPrefix]); + + bool containsUserName(String name) => + _users.values.any((u) => u.name == name); + + void removeUser(Channel channel) { + _users.remove(channel); + } + + /// Retrieves the user associated with the channel. + SetonixUser? getUser(Channel channel) => _users[channel]; + + /// Retrieves a user by name. + SetonixUser? getUserByName(String name) { + for (var user in _users.values) { + if (user.name == name) { + return user; + } + } + return null; + } + + String _generateGuestName() { + String name; + do { + name = '$guestPrefix$_nextGuestId'; + _nextGuestId++; + } while (containsUserName(name)); + return name; + } + + bool addUser(Channel channel, [Uint8List? publicKey, String? name]) { + name ??= _generateGuestName(); + if (containsUserName(name)) { + return false; + } + _users[channel] = SetonixUser(publicKey: publicKey, name: name); + return true; + } +} diff --git a/api/lib/src/services/user.mapper.dart b/api/lib/src/services/user.mapper.dart new file mode 100644 index 00000000..2f7b0947 --- /dev/null +++ b/api/lib/src/services/user.mapper.dart @@ -0,0 +1,119 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member +// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter + +part of 'user.dart'; + +class SetonixUserMapper extends ClassMapperBase { + SetonixUserMapper._(); + + static SetonixUserMapper? _instance; + static SetonixUserMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = SetonixUserMapper._()); + } + return _instance!; + } + + @override + final String id = 'SetonixUser'; + + static Uint8List? _$publicKey(SetonixUser v) => v.publicKey; + static const Field _f$publicKey = + Field('publicKey', _$publicKey, opt: true); + static String _$name(SetonixUser v) => v.name; + static const Field _f$name = Field('name', _$name); + + @override + final MappableFields fields = const { + #publicKey: _f$publicKey, + #name: _f$name, + }; + + static SetonixUser _instantiate(DecodingData data) { + return SetonixUser( + publicKey: data.dec(_f$publicKey), name: data.dec(_f$name)); + } + + @override + final Function instantiate = _instantiate; + + static SetonixUser fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static SetonixUser fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin SetonixUserMappable { + String toJson() { + return SetonixUserMapper.ensureInitialized() + .encodeJson(this as SetonixUser); + } + + Map toMap() { + return SetonixUserMapper.ensureInitialized() + .encodeMap(this as SetonixUser); + } + + SetonixUserCopyWith get copyWith => + _SetonixUserCopyWithImpl( + this as SetonixUser, $identity, $identity); + @override + String toString() { + return SetonixUserMapper.ensureInitialized() + .stringifyValue(this as SetonixUser); + } + + @override + bool operator ==(Object other) { + return SetonixUserMapper.ensureInitialized() + .equalsValue(this as SetonixUser, other); + } + + @override + int get hashCode { + return SetonixUserMapper.ensureInitialized().hashValue(this as SetonixUser); + } +} + +extension SetonixUserValueCopy<$R, $Out> + on ObjectCopyWith<$R, SetonixUser, $Out> { + SetonixUserCopyWith<$R, SetonixUser, $Out> get $asSetonixUser => + $base.as((v, t, t2) => _SetonixUserCopyWithImpl<$R, $Out>(v, t, t2)); +} + +abstract class SetonixUserCopyWith<$R, $In extends SetonixUser, $Out> + implements ClassCopyWith<$R, $In, $Out> { + $R call({Uint8List? publicKey, String? name}); + SetonixUserCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +} + +class _SetonixUserCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, SetonixUser, $Out> + implements SetonixUserCopyWith<$R, SetonixUser, $Out> { + _SetonixUserCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + SetonixUserMapper.ensureInitialized(); + @override + $R call({Object? publicKey = $none, String? name}) => + $apply(FieldCopyWithData({ + if (publicKey != $none) #publicKey: publicKey, + if (name != null) #name: name + })); + @override + SetonixUser $make(CopyWithData data) => SetonixUser( + publicKey: data.get(#publicKey, or: $value.publicKey), + name: data.get(#name, or: $value.name)); + + @override + SetonixUserCopyWith<$R2, SetonixUser, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _SetonixUserCopyWithImpl<$R2, $Out2>($value, $cast, t); +} diff --git a/api/pubspec.lock b/api/pubspec.lock index eb387ce7..2763daf2 100644 --- a/api/pubspec.lock +++ b/api/pubspec.lock @@ -161,6 +161,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" + cryptography_plus: + dependency: "direct main" + description: + name: cryptography_plus + sha256: "34db787df4f4740a39474b6fb0a610aa6dc13a5b5b68754b4787a79939ac0454" + url: "https://pub.dev" + source: hosted + version: "2.7.1" dart_leap: dependency: "direct main" description: diff --git a/api/pubspec.yaml b/api/pubspec.yaml index 90c8cc0c..9f58782d 100644 --- a/api/pubspec.yaml +++ b/api/pubspec.yaml @@ -29,6 +29,7 @@ dependencies: crypto: ^3.0.5 collection: ^1.18.0 convert: ^3.1.2 + cryptography_plus: ^2.7.1 # path: ^1.8.0 dev_dependencies: diff --git a/app/lib/bloc/multiplayer.dart b/app/lib/bloc/multiplayer.dart index a6b32f79..59f4e153 100644 --- a/app/lib/bloc/multiplayer.dart +++ b/app/lib/bloc/multiplayer.dart @@ -33,6 +33,8 @@ sealed class MultiplayerState with MultiplayerStateMappable { bool get isConnected => this is MultiplayerConnectedState; bool get isClient => false; bool get isServer => false; + + UserManager? get userManager => null; } @MappableClass() @@ -62,6 +64,8 @@ final class MultiplayerConnectedState extends MultiplayerState with MultiplayerConnectedStateMappable { final NetworkerBase networker; final SimpleNetworkerPipe pipe; + @override + final UserManager userManager = UserManager(); MultiplayerConnectedState(this.networker, this.pipe); @@ -136,7 +140,8 @@ class MultiplayerCubit extends Cubit { if (base is NetworkerClient) { transformer.connect(pipe); } else if (base is NetworkerServer) { - base.clientConnect.listen(_initController.add); + base.clientConnect.listen(_onJoin); + base.clientDisconnect.listen(_onLeft); transformer.connect(SimpleNetworkerPipe() ..read.listen(_onClientEvent) ..write.listen((e) => _onClientEvent(e, true)) @@ -309,4 +314,15 @@ class MultiplayerCubit extends Cubit { _fatalError = e; disconnect(); } + + void _onLeft((Channel, ConnectionInfo) event) { + state.userManager?.removeUser(event.$1); + } + + void _onJoin((Channel, ConnectionInfo) event) { + _initController.add(event); + state.userManager?.addUser( + event.$1, + ); + } } diff --git a/app/lib/bloc/world/bloc.dart b/app/lib/bloc/world/bloc.dart index 81c96e05..9c3fe216 100644 --- a/app/lib/bloc/world/bloc.dart +++ b/app/lib/bloc/world/bloc.dart @@ -1,5 +1,4 @@ import 'package:bloc_concurrency/bloc_concurrency.dart'; -import 'package:cryptography_plus/cryptography_plus.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart' show ColorScheme; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -154,9 +153,14 @@ class WorldBloc extends Bloc { return state.fileSystem.worldSystem.updateFile(name, data); } - void _processEvent(NetworkerPacket data) { - final value = processClientEvent(data.data, data.channel, state.world, - assetManager: state.assetManager); + Future _processEvent(NetworkerPacket data) async { + final value = await processClientEvent( + data.data, + data.channel, + state.world, + assetManager: state.assetManager, + userManager: state.multiplayer.state.userManager, + ); if (value == null) return; state.multiplayer.sendServerPackets( value.buildPackets(state.world, state.multiplayer.clients)); @@ -170,7 +174,7 @@ class WorldBloc extends Bloc { } } - void process(WorldEvent event) { + Future process(WorldEvent event) async { switch (event) { case LocalWorldEvent e: add(e); @@ -179,7 +183,8 @@ class WorldBloc extends Bloc { if (multiplayer.isConnected) { multiplayer.send(e); } else { - final event = processClientEvent(e, kAuthorityChannel, state.world, + final event = await processClientEvent( + e, kAuthorityChannel, state.world, assetManager: state.assetManager, allowServerEvents: true); if (event != null) { add(event.main.data); @@ -202,23 +207,4 @@ class WorldBloc extends Bloc { // ignore: empty_catches } catch (e) {} } - - Future authenticate(SetonixAccount account) async { - final challenge = state.world.authRequest?.challenge; - if (challenge == null) { - return; - } - final generator = Ed25519(); - final keyPair = SimpleKeyPairData( - account.privateKey, - publicKey: SimplePublicKey(account.publicKey, type: KeyPairType.ed25519), - type: KeyPairType.ed25519, - ); - final signature = await generator.sign(challenge, keyPair: keyPair); - final request = AuthenticateRequest( - Uint8List.fromList(signature.bytes), - account.publicKey, - ); - process(request); - } } diff --git a/app/lib/pages/game/auth.dart b/app/lib/pages/game/auth.dart index 0fd70dcf..0f0b885a 100644 --- a/app/lib/pages/game/auth.dart +++ b/app/lib/pages/game/auth.dart @@ -109,10 +109,12 @@ class _AuthGameViewState extends State { return ListTile( title: Text(account.name), subtitle: Text(account.getFingerprint(true)), - onTap: () { - context - .read() - .authenticate(account); + onTap: () async { + final bloc = context.read(); + final event = + await AuthenticateRequest.build( + authRequest, account); + bloc.process(event); }, ); }, diff --git a/server/lib/src/asset.mapper.dart b/server/lib/src/asset.mapper.dart index 40f7a854..29f32ce9 100644 --- a/server/lib/src/asset.mapper.dart +++ b/server/lib/src/asset.mapper.dart @@ -58,8 +58,8 @@ mixin ServerDataMetadataMappable { ServerDataMetadataCopyWith - get copyWith => _ServerDataMetadataCopyWithImpl( - this as ServerDataMetadata, $identity, $identity); + get copyWith => _ServerDataMetadataCopyWithImpl(this as ServerDataMetadata, $identity, $identity); @override String toString() { return ServerDataMetadataMapper.ensureInitialized() @@ -82,8 +82,8 @@ mixin ServerDataMetadataMappable { extension ServerDataMetadataValueCopy<$R, $Out> on ObjectCopyWith<$R, ServerDataMetadata, $Out> { ServerDataMetadataCopyWith<$R, ServerDataMetadata, $Out> - get $asServerDataMetadata => - $base.as((v, t, t2) => _ServerDataMetadataCopyWithImpl(v, t, t2)); + get $asServerDataMetadata => $base.as( + (v, t, t2) => _ServerDataMetadataCopyWithImpl<$R, $Out>(v, t, t2)); } abstract class ServerDataMetadataCopyWith<$R, $In extends ServerDataMetadata, @@ -118,5 +118,5 @@ class _ServerDataMetadataCopyWithImpl<$R, $Out> @override ServerDataMetadataCopyWith<$R2, ServerDataMetadata, $Out2> $chain<$R2, $Out2>( Then<$Out2, $R2> t) => - _ServerDataMetadataCopyWithImpl($value, $cast, t); + _ServerDataMetadataCopyWithImpl<$R2, $Out2>($value, $cast, t); } diff --git a/server/lib/src/bloc.dart b/server/lib/src/bloc.dart index 84dcc372..231a7056 100644 --- a/server/lib/src/bloc.dart +++ b/server/lib/src/bloc.dart @@ -6,10 +6,16 @@ import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:consoler/consoler.dart'; import 'package:setonix_server/setonix_server.dart'; -Future _computeEvent(ServerWorldEvent event, WorldState state, - List signature) { - return Isolate.run( - () => processServerEvent(event, state, signature: signature)); +Future _computeEvent( + ServerWorldEvent event, + WorldState state, { + required List signature, +}) { + return Isolate.run(() => processServerEvent( + event, + state, + signature: signature, + )); } class WorldBloc extends Bloc @@ -40,8 +46,11 @@ class WorldBloc extends Bloc _serverPlugin = _pluginSystem.registerPlugin('', SetonixPlugin.new); on((event, emit) async { final signature = assetManager.createSignature(); - final processed = - await _computeEvent(event, state, signature.values.toList()); + final processed = await _computeEvent( + event, + state, + signature: signature.values.toList(), + ); final newState = processed.state; processed.responses.forEach(process); if (event is WorldInitialized) { @@ -62,7 +71,7 @@ class WorldBloc extends Bloc Future _loadScript(String? script) async { try { if (script == null) return; - server.pluginSystem.loadLuaPlugin(assetManager, script); + pluginSystem.loadLuaPlugin(assetManager, script); } catch (e) { server.log('Error loading script: $e', level: LogLevel.error); } @@ -89,17 +98,19 @@ class WorldBloc extends Bloc await file.writeAsBytes(bytes); } - void onClientEvent(NetworkerPacket packet, + Future onClientEvent(NetworkerPacket packet, {bool force = false}) async { final data = packet.data; ServerResponse? process; try { - process = processClientEvent( + process = await processClientEvent( data is UserJoined ? null : data, packet.channel, state, assetManager: assetManager, allowServerEvents: packet.isServer, + challengeManager: server.challengeManager, + userManager: server.userManager, ); } catch (e) { server.log('Error processing event: $e', level: LogLevel.error); diff --git a/server/lib/src/config.dart b/server/lib/src/config.dart index d3b34f13..8941af23 100644 --- a/server/lib/src/config.dart +++ b/server/lib/src/config.dart @@ -53,4 +53,7 @@ class ConfigManager { String get worldFile => _mergedConfig.worldFile ?? SetonixConfig.defaultWorldName; + + String get guestPrefix => + _mergedConfig.guestPrefix ?? SetonixConfig.defaultGuestPrefix; } diff --git a/server/lib/src/server.dart b/server/lib/src/server.dart index 355dae16..413ee6d1 100644 --- a/server/lib/src/server.dart +++ b/server/lib/src/server.dart @@ -26,11 +26,12 @@ String limitOutput(Object? value, [int limit = 500]) { final class SetonixServer { final Consoler consoler; - final ConfigManager configManager = ConfigManager(); + final ConfigManager configManager; final ServerAssetManager assetManager; + final UserManager userManager; + final ChallengeManager? challengeManager; final Map _worlds = {}; final Map _userWorlds = {}; - late final PluginSystem pluginSystem; NetworkerSocketServer? _server; NetworkerPipe? _pipe; @@ -67,7 +68,13 @@ final class SetonixServer { WorldState? getUserWorldState(Channel channel) => getUserWorld(channel)?.state; - SetonixServer._(this.consoler, this.assetManager); + SetonixServer._( + this.consoler, + this.assetManager, + this.configManager, + this.userManager, + this.challengeManager, + ); static Future load({ String? worldFile, @@ -80,7 +87,11 @@ final class SetonixServer { ); await _runStaticLogZone( consoler, () => assetManager.init(console: consoler)); - return SetonixServer._(consoler, assetManager); + final configManager = ConfigManager(); + final userManager = UserManager(configManager.guestPrefix); + final challengeManager = ChallengeManager(); + return SetonixServer._( + consoler, assetManager, configManager, userManager, challengeManager); } void log(Object? message, {LogLevel? level}) => @@ -205,6 +216,10 @@ final class SetonixServer { final (user, info) = event; log('${info.address} ($user) left the game', level: LogLevel.info); getUserWorld(user)?.eventSystem.runLeaveCallback(event.$1, event.$2); + + _userWorlds.remove(user); + userManager.removeUser(user); + challengeManager?.removeChallenge(user); } Future saveAll({bool force = false}) async { diff --git a/server/pubspec.lock b/server/pubspec.lock index 8b890933..5a81376c 100644 --- a/server/pubspec.lock +++ b/server/pubspec.lock @@ -210,6 +210,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" + cryptography_plus: + dependency: transitive + description: + name: cryptography_plus + sha256: "34db787df4f4740a39474b6fb0a610aa6dc13a5b5b68754b4787a79939ac0454" + url: "https://pub.dev" + source: hosted + version: "2.7.1" dart_leap: dependency: transitive description: From ddf86581d90297a0b9dcaadfdc1b3af0eefee987 Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Sat, 14 Jun 2025 12:45:25 +0200 Subject: [PATCH 08/18] Fix auth events --- api/lib/src/event/client.dart | 2 +- api/lib/src/event/event.mapper.dart | 2 ++ api/lib/src/event/process/server.dart | 1 + api/lib/src/event/server.dart | 19 ++++++++++++++++++- api/lib/src/services/user.dart | 5 ++--- api/lib/src/services/user.mapper.dart | 1 + app/lib/pages/game/auth.dart | 4 ++-- app/lib/pages/game/page.dart | 9 +++++---- app/lib/pages/settings/accounts.dart | 11 +++++++++++ 9 files changed, 43 insertions(+), 11 deletions(-) diff --git a/api/lib/src/event/client.dart b/api/lib/src/event/client.dart index eeb9f889..94621919 100644 --- a/api/lib/src/event/client.dart +++ b/api/lib/src/event/client.dart @@ -158,7 +158,7 @@ final class ModeChangeRequest extends ClientWorldEvent ModeChangeRequest.plain() : location = null; } -@MappableClass() +@MappableClass(includeCustomMappers: [Base64Uint8ListHook()]) final class AuthenticateRequest extends ClientWorldEvent with AuthenticateRequestMappable { final Uint8List signature; diff --git a/api/lib/src/event/event.mapper.dart b/api/lib/src/event/event.mapper.dart index 6f96a337..e1933ce9 100644 --- a/api/lib/src/event/event.mapper.dart +++ b/api/lib/src/event/event.mapper.dart @@ -1784,6 +1784,7 @@ class AuthenticatedRequestedMapper if (_instance == null) { MapperContainer.globals.use(_instance = AuthenticatedRequestedMapper._()); ServerWorldEventMapper.ensureInitialized().addSubMapper(_instance!); + MapperContainer.globals.useAll([Base64Uint8ListHook()]); } return _instance!; } @@ -3511,6 +3512,7 @@ class AuthenticateRequestMapper if (_instance == null) { MapperContainer.globals.use(_instance = AuthenticateRequestMapper._()); ClientWorldEventMapper.ensureInitialized().addSubMapper(_instance!); + MapperContainer.globals.useAll([Base64Uint8ListHook()]); } return _instance!; } diff --git a/api/lib/src/event/process/server.dart b/api/lib/src/event/process/server.dart index 02fb05e1..1ae4db6d 100644 --- a/api/lib/src/event/process/server.dart +++ b/api/lib/src/event/process/server.dart @@ -104,6 +104,7 @@ ServerProcessed processServerEvent( dialogs: event.clearUserInterface ? [] : state.dialogs, images: event.clearUserInterface ? {} : state.images, gameState: GameState.play, + authRequest: null, )); case TeamJoined(): return ServerProcessed(state.copyWith( diff --git a/api/lib/src/event/server.dart b/api/lib/src/event/server.dart index 32c6f42e..591b0b21 100644 --- a/api/lib/src/event/server.dart +++ b/api/lib/src/event/server.dart @@ -187,6 +187,23 @@ final class DialogsClosed extends ServerWorldEvent with DialogsClosedMappable { DialogsClosed.all() : ids = null; } +class Base64Uint8ListHook extends SimpleMapper { + const Base64Uint8ListHook(); + + @override + Uint8List decode(Object value) { + if (value is String) { + return base64Decode(value); + } + return value as Uint8List; + } + + @override + Object? encode(Uint8List self) { + return base64Encode(self); + } +} + class Base64IdMapHook extends SimpleMapper> { const Base64IdMapHook(); @@ -225,7 +242,7 @@ final class ServerStateUpdated extends ServerWorldEvent const ServerStateUpdated(this.state); } -@MappableClass() +@MappableClass(includeCustomMappers: [Base64Uint8ListHook()]) final class AuthenticatedRequested extends ServerWorldEvent with AuthenticatedRequestedMappable { final Uint8List challenge; diff --git a/api/lib/src/services/user.dart b/api/lib/src/services/user.dart index e7cd3017..efbf0806 100644 --- a/api/lib/src/services/user.dart +++ b/api/lib/src/services/user.dart @@ -2,12 +2,11 @@ import 'dart:typed_data'; import 'package:dart_mappable/dart_mappable.dart'; import 'package:networker/networker.dart'; - -import '../models/config.dart'; +import 'package:setonix_api/event.dart'; part 'user.mapper.dart'; -@MappableClass() +@MappableClass(includeCustomMappers: [Base64Uint8ListHook()]) final class SetonixUser with SetonixUserMappable { final Uint8List? publicKey; final String name; diff --git a/api/lib/src/services/user.mapper.dart b/api/lib/src/services/user.mapper.dart index 2f7b0947..d7d8b2e3 100644 --- a/api/lib/src/services/user.mapper.dart +++ b/api/lib/src/services/user.mapper.dart @@ -13,6 +13,7 @@ class SetonixUserMapper extends ClassMapperBase { static SetonixUserMapper ensureInitialized() { if (_instance == null) { MapperContainer.globals.use(_instance = SetonixUserMapper._()); + MapperContainer.globals.useAll([Base64Uint8ListHook()]); } return _instance!; } diff --git a/app/lib/pages/game/auth.dart b/app/lib/pages/game/auth.dart index 0f0b885a..c7cdab34 100644 --- a/app/lib/pages/game/auth.dart +++ b/app/lib/pages/game/auth.dart @@ -55,7 +55,7 @@ class _AuthGameViewState extends State { ResponsiveAlertDialog( title: Text(AppLocalizations.of(context).authenticate), constraints: BoxConstraints( - maxWidth: LeapBreakpoints.medium, + maxWidth: LeapBreakpoints.compact, ), headerActions: [ IconButton( @@ -107,7 +107,7 @@ class _AuthGameViewState extends State { itemBuilder: (context, index) { final account = accounts[index]; return ListTile( - title: Text(account.name), + title: Text(account.name.substring(1)), subtitle: Text(account.getFingerprint(true)), onTap: () async { final bloc = context.read(); diff --git a/app/lib/pages/game/page.dart b/app/lib/pages/game/page.dart index 61577999..f88dd885 100644 --- a/app/lib/pages/game/page.dart +++ b/app/lib/pages/game/page.dart @@ -229,11 +229,12 @@ class _GamePageState extends State { buildWhen: (previous, current) => previous.world.gameState != current.world.gameState, builder: (context, state) { - return Stack(children: [ + return Stack(alignment: Alignment.center, children: [ if (state.world.gameState == GameState.configuration) - Center( - child: Column( + Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(), const SizedBox(height: 16), @@ -244,7 +245,7 @@ class _GamePageState extends State { Theme.of(context).textTheme.bodyMedium, ), ], - )) + ) else GameWidget( game: BoardGame( diff --git a/app/lib/pages/settings/accounts.dart b/app/lib/pages/settings/accounts.dart index 7fb40038..4446a009 100644 --- a/app/lib/pages/settings/accounts.dart +++ b/app/lib/pages/settings/accounts.dart @@ -73,6 +73,17 @@ class _AccountsSettingsPageState extends State { body: FutureBuilder>( future: _keysFuture, builder: (context, state) { + if (state.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } + if (state.hasError) { + return Center( + child: Text( + AppLocalizations.of(context).error, + style: Theme.of(context).textTheme.bodyLarge, + ), + ); + } final accounts = state.data ?? []; return ListView.builder( itemCount: accounts.length, From faf30467c44330c8b17e83391e93629047ab3cde Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Sat, 14 Jun 2025 19:45:01 +0200 Subject: [PATCH 09/18] Improve ui of auth --- app/lib/l10n/app_en.arb | 5 +- app/lib/pages/game/auth.dart | 197 ++++++++++++++++----------- app/lib/pages/game/page.dart | 75 +++++----- app/lib/pages/game/team.dart | 11 +- app/lib/pages/settings/accounts.dart | 8 ++ app/lib/services/file_system.dart | 1 + 6 files changed, 175 insertions(+), 122 deletions(-) diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index 64974429..64daf48c 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -250,9 +250,9 @@ "compact": "Compact", "standard": "Standard", "comfortable": "Comfortable", + "stackedCards": "Stacked cards", "accounts": "Accounts", "create": "Create", - "stackedCards": "Stacked cards", "configuringGame": "Configuring game...", "authenticate": "Authenticate", "authenticateDescription": "The server requested authentication. Select the account you want to use to authenticate.", @@ -261,5 +261,6 @@ "importAccountsDescription": "Are you sure you want to import the accounts?", "backupAllKeys": "Backup all keys", "noAccount": "There are no accounts available", - "authenticateRequired": "Authentication is required to connect to the server" + "authenticateRequired": "Authentication is required to connect to the server", + "authenticateLoading": "Authenticating..." } diff --git a/app/lib/pages/game/auth.dart b/app/lib/pages/game/auth.dart index c7cdab34..f94a9895 100644 --- a/app/lib/pages/game/auth.dart +++ b/app/lib/pages/game/auth.dart @@ -44,90 +44,123 @@ class _AuthGameViewState extends State { if (authRequest == null) { return const SizedBox.shrink(); } - return Stack( - children: [ - BackdropFilter( - filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), - child: Container( - color: Colors.black.withValues(alpha: 0.5), - ), - ), - ResponsiveAlertDialog( - title: Text(AppLocalizations.of(context).authenticate), - constraints: BoxConstraints( - maxWidth: LeapBreakpoints.compact, - ), - headerActions: [ - IconButton( - icon: const Icon(PhosphorIconsLight.gear), - onPressed: () async { - await openSettings(context, view: SettingsView.accounts); - setState(() { - _buildKeysFuture(); - }); - }, - ), - ], - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(AppLocalizations.of(context).authenticateDescription), - if (authRequest.isRequired) - Text( - AppLocalizations.of(context).authenticateRequired, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Colors.red, - ), + bool isLoading = false; + return StatefulBuilder( + builder: (context, setInnerState) => Stack( + alignment: Alignment.center, + children: [ + BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), + child: Container( + color: Colors.black.withValues(alpha: 0.5), + ), ), - Flexible( - child: FutureBuilder>( - future: _keysFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == - ConnectionState.waiting) { - return const Center( - child: CircularProgressIndicator()); - } else if (snapshot.hasError) { - return Center( - child: Text( - AppLocalizations.of(context).error, + ResponsiveAlertDialog( + title: Text(AppLocalizations.of(context).authenticate), + constraints: BoxConstraints( + maxWidth: LeapBreakpoints.compact, + ), + headerActions: [ + IconButton( + icon: const Icon(PhosphorIconsLight.gear), + onPressed: () async { + await openSettings(context, + view: SettingsView.accounts); + setState(() { + _buildKeysFuture(); + }); + }, + ), + ], + content: isLoading + ? Column( + mainAxisSize: MainAxisSize.min, + children: [ + CircularProgressIndicator(), + const SizedBox(height: 16.0), + Text(AppLocalizations.of(context) + .authenticateLoading), + ], + ) + : Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(AppLocalizations.of(context) + .authenticateDescription), + if (authRequest.isRequired) + Text( + AppLocalizations.of(context) + .authenticateRequired, + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith( + color: Colors.red, + ), + ), + const SizedBox(height: 16.0), + Flexible( + child: FutureBuilder>( + future: _keysFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == + ConnectionState.waiting) { + return const Padding( + padding: EdgeInsets.all(16.0), + child: CircularProgressIndicator(), + ); + } else if (snapshot.hasError) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + AppLocalizations.of(context) + .error, + ), + ); + } else if (!snapshot.hasData || + snapshot.data!.isEmpty) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + AppLocalizations.of(context) + .noAccount), + ); + } else { + final accounts = snapshot.data!; + return ListView.builder( + shrinkWrap: true, + itemCount: accounts.length, + itemBuilder: (context, index) { + final account = accounts[index]; + return ListTile( + title: Text( + account.name.substring(1)), + subtitle: Text(account + .getFingerprint(true)), + onTap: () async { + final bloc = + context.read(); + final event = + await AuthenticateRequest + .build(authRequest, + account); + bloc.process(event); + setInnerState(() { + isLoading = true; + }); + }, + ); + }, + ); + } + }, + ), + ), + ], ), - ); - } else if (!snapshot.hasData || - snapshot.data!.isEmpty) { - return Center( - child: - Text(AppLocalizations.of(context).noAccount), - ); - } else { - final accounts = snapshot.data!; - return ListView.builder( - shrinkWrap: true, - itemCount: accounts.length, - itemBuilder: (context, index) { - final account = accounts[index]; - return ListTile( - title: Text(account.name.substring(1)), - subtitle: Text(account.getFingerprint(true)), - onTap: () async { - final bloc = context.read(); - final event = - await AuthenticateRequest.build( - authRequest, account); - bloc.process(event); - }, - ); - }, - ); - } - }, - ), - ), - ], - ), - ) - ], - ); + ) + ], + )); }); } } diff --git a/app/lib/pages/game/page.dart b/app/lib/pages/game/page.dart index f88dd885..1600a315 100644 --- a/app/lib/pages/game/page.dart +++ b/app/lib/pages/game/page.dart @@ -229,42 +229,49 @@ class _GamePageState extends State { buildWhen: (previous, current) => previous.world.gameState != current.world.gameState, builder: (context, state) { - return Stack(alignment: Alignment.center, children: [ - if (state.world.gameState == - GameState.configuration) - Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - CircularProgressIndicator(), - const SizedBox(height: 16), - Text( - AppLocalizations.of(context) - .configuringGame, - style: - Theme.of(context).textTheme.bodyMedium, + return Center( + child: + Stack(alignment: Alignment.center, children: [ + if (state.world.gameState == + GameState.configuration) + Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator(), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context) + .configuringGame, + style: Theme.of(context) + .textTheme + .bodyMedium, + ), + ], + ) + else + GameWidget( + game: BoardGame( + bloc: context.read(), + settingsCubit: + context.read(), + contextMenuController: + _contextMenuController, + onEscape: () => + Scaffold.of(context).openDrawer(), ), - ], - ) - else - GameWidget( - game: BoardGame( - bloc: context.read(), - settingsCubit: context.read(), - contextMenuController: _contextMenuController, - onEscape: () => - Scaffold.of(context).openDrawer(), + focusNode: _focusNode, + initialActiveOverlays: ['dialogs', 'filter'], + overlayBuilderMap: { + 'dialogs': (context, game) => + GameDialogOverlay(), + 'filter': (context, game) => + GameFilterView(), + }, ), - focusNode: _focusNode, - initialActiveOverlays: ['dialogs', 'filter'], - overlayBuilderMap: { - 'dialogs': (context, game) => - GameDialogOverlay(), - 'filter': (context, game) => GameFilterView(), - }, - ), - AuthGameView(), - ]); + AuthGameView(), + ]), + ); }, ), ); diff --git a/app/lib/pages/game/team.dart b/app/lib/pages/game/team.dart index a626defb..72dae490 100644 --- a/app/lib/pages/game/team.dart +++ b/app/lib/pages/game/team.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; import 'package:setonix/src/generated/i18n/app_localizations.dart'; import 'package:material_leap/material_leap.dart'; import 'package:setonix/bloc/world/bloc.dart'; @@ -90,9 +91,10 @@ class _TeamDialogState extends State { ), ), actions: [ - TextButton( + TextButton.icon( onPressed: () => Navigator.of(context).pop(), - child: Text(AppLocalizations.of(context).cancel), + icon: const Icon(PhosphorIconsLight.prohibit), + label: Text(AppLocalizations.of(context).cancel), ), BlocBuilder( bloc: _bloc, @@ -102,7 +104,7 @@ class _TeamDialogState extends State { listenable: _nameController, builder: (context, child) { final text = _nameController.text; - return ElevatedButton( + return ElevatedButton.icon( onPressed: text.isEmpty || (state.info.teams.containsKey(text) && text != widget.team) @@ -120,7 +122,8 @@ class _TeamDialogState extends State { team)); Navigator.of(context).pop(); }, - child: Text(_isCreate() + icon: const Icon(PhosphorIconsLight.check), + label: Text(_isCreate() ? LeapLocalizations.of(context).create : AppLocalizations.of(context).edit), ); diff --git a/app/lib/pages/settings/accounts.dart b/app/lib/pages/settings/accounts.dart index 4446a009..a3f838c8 100644 --- a/app/lib/pages/settings/accounts.dart +++ b/app/lib/pages/settings/accounts.dart @@ -85,6 +85,14 @@ class _AccountsSettingsPageState extends State { ); } final accounts = state.data ?? []; + if (accounts.isEmpty) { + return Center( + child: Text( + AppLocalizations.of(context).noAccount, + style: Theme.of(context).textTheme.bodyLarge, + ), + ); + } return ListView.builder( itemCount: accounts.length, itemBuilder: (context, index) { diff --git a/app/lib/services/file_system.dart b/app/lib/services/file_system.dart index f42a882b..434a5869 100644 --- a/app/lib/services/file_system.dart +++ b/app/lib/services/file_system.dart @@ -272,6 +272,7 @@ class SetonixFileSystem { } Future> getAccounts([List? names]) async { + await privateKeySystem.initialize(); names ??= await privateKeySystem.getKeys(); return Future.wait( names.map((name) => getAccount(name)), From 255e3e2441ea2cf66baa80e64e52348b63701ef3 Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Sun, 15 Jun 2025 23:52:06 +0200 Subject: [PATCH 10/18] Add server list --- docs/public/favicon.svg | 1 - plugin/pubspec.lock | 8 + server/lib/src/config.dart | 4 +- servers/.gitignore | 24 + servers/README.md | 48 + servers/astro.config.mjs | 16 + servers/package.json | 16 + servers/pnpm-lock.yaml | 3151 ++++++++++++++++++++++ servers/public/favicon.svg | 92 + servers/src/assets/astro.svg | 1 + servers/src/assets/background.svg | 1 + servers/src/components/ServerEntry.astro | 21 + servers/src/data/servers.json | 3 + servers/src/layouts/Layout.astro | 21 + servers/src/pages/data.json.ts | 10 + servers/src/pages/index.astro | 23 + servers/src/scripts/servers/info.ts | 43 + servers/src/scripts/servers/list.ts | 58 + servers/src/scripts/servers/utils.ts | 18 + servers/tsconfig.json | 5 + 20 files changed, 3561 insertions(+), 3 deletions(-) delete mode 100644 docs/public/favicon.svg create mode 100644 servers/.gitignore create mode 100644 servers/README.md create mode 100644 servers/astro.config.mjs create mode 100644 servers/package.json create mode 100644 servers/pnpm-lock.yaml create mode 100644 servers/public/favicon.svg create mode 100644 servers/src/assets/astro.svg create mode 100644 servers/src/assets/background.svg create mode 100644 servers/src/components/ServerEntry.astro create mode 100644 servers/src/data/servers.json create mode 100644 servers/src/layouts/Layout.astro create mode 100644 servers/src/pages/data.json.ts create mode 100644 servers/src/pages/index.astro create mode 100644 servers/src/scripts/servers/info.ts create mode 100644 servers/src/scripts/servers/list.ts create mode 100644 servers/src/scripts/servers/utils.ts create mode 100644 servers/tsconfig.json diff --git a/docs/public/favicon.svg b/docs/public/favicon.svg deleted file mode 100644 index cba5ac14..00000000 --- a/docs/public/favicon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/plugin/pubspec.lock b/plugin/pubspec.lock index 90c7d267..305eb77b 100644 --- a/plugin/pubspec.lock +++ b/plugin/pubspec.lock @@ -177,6 +177,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" + cryptography_plus: + dependency: transitive + description: + name: cryptography_plus + sha256: "34db787df4f4740a39474b6fb0a610aa6dc13a5b5b68754b4787a79939ac0454" + url: "https://pub.dev" + source: hosted + version: "2.7.1" dart_leap: dependency: transitive description: diff --git a/server/lib/src/config.dart b/server/lib/src/config.dart index 8941af23..5383c9f7 100644 --- a/server/lib/src/config.dart +++ b/server/lib/src/config.dart @@ -31,8 +31,8 @@ class ConfigManager { _mergedConfig = _mergeConfig(); } else { _config = SetonixConfig.defaultConfig; - await file.writeAsString( - JsonEncoder.withIndent(' ').convert(_config.toJson())); + await file + .writeAsString(JsonEncoder.withIndent(' ').convert(_config.toMap())); } } diff --git a/servers/.gitignore b/servers/.gitignore new file mode 100644 index 00000000..016b59ea --- /dev/null +++ b/servers/.gitignore @@ -0,0 +1,24 @@ +# build output +dist/ + +# generated types +.astro/ + +# dependencies +node_modules/ + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# environment variables +.env +.env.production + +# macOS-specific files +.DS_Store + +# jetbrains setting folder +.idea/ diff --git a/servers/README.md b/servers/README.md new file mode 100644 index 00000000..a64188fd --- /dev/null +++ b/servers/README.md @@ -0,0 +1,48 @@ +# Astro Starter Kit: Basics + +```sh +pnpm create astro@latest -- --template basics +``` + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics) +[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics) +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json) + +> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! + +![just-the-basics](https://github.com/withastro/astro/assets/2244813/a0a5533c-a856-4198-8470-2d67b1d7c554) + +## 🚀 Project Structure + +Inside of your Astro project, you'll see the following folders and files: + +```text +/ +├── public/ +│ └── favicon.svg +├── src/ +│ ├── layouts/ +│ │ └── Layout.astro +│ └── pages/ +│ └── index.astro +└── package.json +``` + +To learn more about the folder structure of an Astro project, refer to [our guide on project structure](https://docs.astro.build/en/basics/project-structure/). + +## 🧞 Commands + +All commands are run from the root of the project, from a terminal: + +| Command | Action | +| :------------------------ | :----------------------------------------------- | +| `pnpm install` | Installs dependencies | +| `pnpm dev` | Starts local dev server at `localhost:4321` | +| `pnpm build` | Build your production site to `./dist/` | +| `pnpm preview` | Preview your build locally, before deploying | +| `pnpm astro ...` | Run CLI commands like `astro add`, `astro check` | +| `pnpm astro -- --help` | Get help using the Astro CLI | + +## 👀 Want to learn more? + +Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). diff --git a/servers/astro.config.mjs b/servers/astro.config.mjs new file mode 100644 index 00000000..a1eef492 --- /dev/null +++ b/servers/astro.config.mjs @@ -0,0 +1,16 @@ +// @ts-check +import { defineConfig, envField } from "astro/config"; + +// https://astro.build/config +export default defineConfig({ + output: "server", + env: { + schema: { + REMOTE_URLS: envField.string({ + context: "server", + access: "secret", + optional: true, + }), + }, + }, +}); diff --git a/servers/package.json b/servers/package.json new file mode 100644 index 00000000..458ce235 --- /dev/null +++ b/servers/package.json @@ -0,0 +1,16 @@ +{ + "name": "servers", + "type": "module", + "version": "0.0.1", + "scripts": { + "dev": "astro dev", + "build": "astro build", + "preview": "astro preview", + "astro": "astro" + }, + "packageManager": "pnpm@10.12.1", + "dependencies": { + "astro": "^5.9.3", + "@cacheable/node-cache": "^1.5.6" + } +} \ No newline at end of file diff --git a/servers/pnpm-lock.yaml b/servers/pnpm-lock.yaml new file mode 100644 index 00000000..3fc12248 --- /dev/null +++ b/servers/pnpm-lock.yaml @@ -0,0 +1,3151 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@cacheable/node-cache': + specifier: ^1.5.6 + version: 1.5.6 + astro: + specifier: ^5.9.3 + version: 5.9.3(@types/node@24.0.1)(rollup@4.43.0)(typescript@5.8.3) + +packages: + + '@astrojs/compiler@2.12.2': + resolution: {integrity: sha512-w2zfvhjNCkNMmMMOn5b0J8+OmUaBL1o40ipMvqcG6NRpdC+lKxmTi48DT8Xw0SzJ3AfmeFLB45zXZXtmbsjcgw==} + + '@astrojs/internal-helpers@0.6.1': + resolution: {integrity: sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A==} + + '@astrojs/markdown-remark@6.3.2': + resolution: {integrity: sha512-bO35JbWpVvyKRl7cmSJD822e8YA8ThR/YbUsciWNA7yTcqpIAL2hJDToWP5KcZBWxGT6IOdOkHSXARSNZc4l/Q==} + + '@astrojs/prism@3.3.0': + resolution: {integrity: sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + + '@astrojs/telemetry@3.3.0': + resolution: {integrity: sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.27.5': + resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.27.6': + resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==} + engines: {node: '>=6.9.0'} + + '@cacheable/node-cache@1.5.6': + resolution: {integrity: sha512-1sLMrvrWoxrqiVKW1BFDjYrBGSG32miOJfNyPUaWwisvbsu+V2wgYqPyLiw0qpprxtjVZT02omsGgQut+DKw8Q==} + + '@capsizecss/unpack@2.4.0': + resolution: {integrity: sha512-GrSU71meACqcmIUxPYOJvGKF0yryjN/L1aCuE9DViCTJI7bfkjgYDPD1zbNDcINJwSSP6UaBZY9GAbYDO7re0Q==} + + '@emnapi/runtime@1.4.3': + resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} + + '@esbuild/aix-ppc64@0.25.5': + resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.5': + resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.5': + resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.5': + resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.5': + resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.5': + resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.5': + resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.5': + resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.5': + resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.5': + resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.5': + resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.5': + resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.5': + resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.5': + resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.5': + resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.5': + resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.5': + resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.5': + resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.5': + resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.5': + resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.5': + resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.25.5': + resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.5': + resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.5': + resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.5': + resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@img/sharp-darwin-arm64@0.33.5': + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.33.5': + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.0.4': + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.0.4': + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.0.4': + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.0.5': + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.0.4': + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.0.4': + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.33.5': + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.33.5': + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-s390x@0.33.5': + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.33.5': + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.33.5': + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.33.5': + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.33.5': + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-ia32@0.33.5': + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.33.5': + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@keyv/serialize@1.0.3': + resolution: {integrity: sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g==} + + '@oslojs/encoding@1.1.0': + resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} + + '@rollup/pluginutils@5.1.4': + resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.43.0': + resolution: {integrity: sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.43.0': + resolution: {integrity: sha512-ss4YJwRt5I63454Rpj+mXCXicakdFmKnUNxr1dLK+5rv5FJgAxnN7s31a5VchRYxCFWdmnDWKd0wbAdTr0J5EA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.43.0': + resolution: {integrity: sha512-eKoL8ykZ7zz8MjgBenEF2OoTNFAPFz1/lyJ5UmmFSz5jW+7XbH1+MAgCVHy72aG59rbuQLcJeiMrP8qP5d/N0A==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.43.0': + resolution: {integrity: sha512-SYwXJgaBYW33Wi/q4ubN+ldWC4DzQY62S4Ll2dgfr/dbPoF50dlQwEaEHSKrQdSjC6oIe1WgzosoaNoHCdNuMg==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.43.0': + resolution: {integrity: sha512-SV+U5sSo0yujrjzBF7/YidieK2iF6E7MdF6EbYxNz94lA+R0wKl3SiixGyG/9Klab6uNBIqsN7j4Y/Fya7wAjQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.43.0': + resolution: {integrity: sha512-J7uCsiV13L/VOeHJBo5SjasKiGxJ0g+nQTrBkAsmQBIdil3KhPnSE9GnRon4ejX1XDdsmK/l30IYLiAaQEO0Cg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.43.0': + resolution: {integrity: sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.43.0': + resolution: {integrity: sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.43.0': + resolution: {integrity: sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.43.0': + resolution: {integrity: sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.43.0': + resolution: {integrity: sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.43.0': + resolution: {integrity: sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.43.0': + resolution: {integrity: sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.43.0': + resolution: {integrity: sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.43.0': + resolution: {integrity: sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.43.0': + resolution: {integrity: sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.43.0': + resolution: {integrity: sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.43.0': + resolution: {integrity: sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.43.0': + resolution: {integrity: sha512-fYCTEyzf8d+7diCw8b+asvWDCLMjsCEA8alvtAutqJOJp/wL5hs1rWSqJ1vkjgW0L2NB4bsYJrpKkiIPRR9dvw==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.43.0': + resolution: {integrity: sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw==} + cpu: [x64] + os: [win32] + + '@shikijs/core@3.6.0': + resolution: {integrity: sha512-9By7Xb3olEX0o6UeJyPLI1PE1scC4d3wcVepvtv2xbuN9/IThYN4Wcwh24rcFeASzPam11MCq8yQpwwzCgSBRw==} + + '@shikijs/engine-javascript@3.6.0': + resolution: {integrity: sha512-7YnLhZG/TU05IHMG14QaLvTW/9WiK8SEYafceccHUSXs2Qr5vJibUwsDfXDLmRi0zHdzsxrGKpSX6hnqe0k8nA==} + + '@shikijs/engine-oniguruma@3.6.0': + resolution: {integrity: sha512-nmOhIZ9yT3Grd+2plmW/d8+vZ2pcQmo/UnVwXMUXAKTXdi+LK0S08Ancrz5tQQPkxvjBalpMW2aKvwXfelauvA==} + + '@shikijs/langs@3.6.0': + resolution: {integrity: sha512-IdZkQJaLBu1LCYCwkr30hNuSDfllOT8RWYVZK1tD2J03DkiagYKRxj/pDSl8Didml3xxuyzUjgtioInwEQM/TA==} + + '@shikijs/themes@3.6.0': + resolution: {integrity: sha512-Fq2j4nWr1DF4drvmhqKq8x5vVQ27VncF8XZMBuHuQMZvUSS3NBgpqfwz/FoGe36+W6PvniZ1yDlg2d4kmYDU6w==} + + '@shikijs/types@3.6.0': + resolution: {integrity: sha512-cLWFiToxYu0aAzJqhXTQsFiJRTFDAGl93IrMSBNaGSzs7ixkLfdG6pH11HipuWFGW5vyx4X47W8HDQ7eSrmBUg==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@swc/helpers@0.5.17': + resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/estree@1.0.7': + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/fontkit@2.0.8': + resolution: {integrity: sha512-wN+8bYxIpJf+5oZdrdtaX04qUuWHcKxcDEgRS9Qm9ZClSHjzEn13SxUC+5eRM+4yXIeTYk8mTzLAWGF64847ew==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/nlcst@2.0.3': + resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==} + + '@types/node@24.0.1': + resolution: {integrity: sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-iterate@2.0.1: + resolution: {integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==} + + astro@5.9.3: + resolution: {integrity: sha512-VReZrpUa/3rfeiVvsQ1A2M3ujDPI+pDGIYOMtXPEZwut8tZoEyealXXLjitgCsJ+3dunKGZbg4Eak6i+r0vniw==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} + hasBin: true + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + base-64@1.0.0: + resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + blob-to-buffer@1.2.9: + resolution: {integrity: sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA==} + + boxen@8.0.1: + resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} + engines: {node: '>=18'} + + brotli@1.3.3: + resolution: {integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + cacheable@1.10.0: + resolution: {integrity: sha512-SSgQTAnhd7WlJXnGlIi4jJJOiHzgnM5wRMEPaXAU4kECTAMpBoYKoZ9i5zHmclIEZbxcu3j7yY/CF8DTmwIsHg==} + + camelcase@8.0.0: + resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} + engines: {node: '>=16'} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chalk@5.4.1: + resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + ci-info@4.2.0: + resolution: {integrity: sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==} + engines: {node: '>=8'} + + cli-boxes@3.0.0: + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} + engines: {node: '>=10'} + + clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + common-ancestor-path@1.0.1: + resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} + + cookie-es@1.2.2: + resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} + + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + + cross-fetch@3.2.0: + resolution: {integrity: sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==} + + crossws@0.3.5: + resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} + + css-tree@3.1.0: + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.2.0: + resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} + engines: {node: '>=8'} + + deterministic-object-hash@2.0.2: + resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==} + engines: {node: '>=18'} + + devalue@5.1.1: + resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + dfa@1.2.0: + resolution: {integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==} + + diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} + engines: {node: '>=0.3.1'} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + dset@3.1.4: + resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} + engines: {node: '>=4'} + + emoji-regex@10.4.0: + resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esbuild@0.25.5: + resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} + engines: {node: '>=18'} + hasBin: true + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fdir@6.4.6: + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + flattie@1.1.1: + resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==} + engines: {node: '>=8'} + + fontace@0.3.0: + resolution: {integrity: sha512-czoqATrcnxgWb/nAkfyIrRp6Q8biYj7nGnL6zfhTcX+JKKpWHFBnb8uNMw/kZr7u++3Y3wYSYoZgHkCcsuBpBg==} + + fontkit@2.0.4: + resolution: {integrity: sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-east-asian-width@1.3.0: + resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==} + engines: {node: '>=18'} + + github-slugger@2.0.0: + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + + h3@1.15.3: + resolution: {integrity: sha512-z6GknHqyX0h9aQaTx22VZDf6QyZn+0Nh+Ym8O/u0SGSkyF5cuTJYKlc8MkzW3Nzf9LE1ivcpmYC3FUGpywhuUQ==} + + hast-util-from-html@2.0.3: + resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} + + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-raw@9.1.0: + resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-to-parse5@8.0.0: + resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==} + + hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + + hookified@1.9.1: + resolution: {integrity: sha512-u3pxtGhKjcSXnGm1CX6aXS9xew535j3lkOCegbA6jdyh0BaAjTbXI4aslKstCr6zUNtoCxFGFKwjbSHdGrMB8g==} + + html-escaper@3.0.3: + resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + import-meta-resolve@4.1.0: + resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} + + iron-webcrypto@1.2.1: + resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} + + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-wsl@3.1.0: + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + engines: {node: '>=16'} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + keyv@5.3.4: + resolution: {integrity: sha512-ypEvQvInNpUe+u+w8BIcPkQvEqXquyyibWE/1NB5T2BTzIpS5cGEV1LZskDzPSTvNAaT4+5FutvzlvnkxOSKlw==} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + mdast-util-definitions@6.0.0: + resolution: {integrity: sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==} + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.0: + resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + mdn-data@2.12.2: + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + neotraverse@0.6.18: + resolution: {integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==} + engines: {node: '>= 10'} + + nlcst-to-string@4.0.0: + resolution: {integrity: sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==} + + node-fetch-native@1.6.6: + resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-mock-http@1.0.0: + resolution: {integrity: sha512-0uGYQ1WQL1M5kKvGRXWQ3uZCHtLTO8hln3oBjIusM75WoesZ909uQJs/Hb946i2SS+Gsrhkaa6iAO17jRIv6DQ==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + ofetch@1.4.1: + resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==} + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + + oniguruma-parser@0.12.1: + resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} + + oniguruma-to-es@4.3.3: + resolution: {integrity: sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==} + + p-limit@6.2.0: + resolution: {integrity: sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==} + engines: {node: '>=18'} + + p-queue@8.1.0: + resolution: {integrity: sha512-mxLDbbGIBEXTJL0zEx8JIylaj3xQ7Z/7eEVjcF9fJX4DBiH9oqe+oahYnlKKxm0Ci9TlWTyhSHgygxMxjIB2jw==} + engines: {node: '>=18'} + + p-timeout@6.1.4: + resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==} + engines: {node: '>=14.16'} + + package-manager-detector@1.3.0: + resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} + + pako@0.2.9: + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + + parse-latin@7.0.0: + resolution: {integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + + postcss@8.5.5: + resolution: {integrity: sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==} + engines: {node: ^10 || ^12 || >=14} + + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + property-information@6.5.0: + resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} + + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + + radix3@1.1.2: + resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.0.1: + resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==} + + rehype-parse@9.0.1: + resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==} + + rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + + rehype-stringify@10.0.1: + resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==} + + rehype@13.0.2: + resolution: {integrity: sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-smartypants@3.0.2: + resolution: {integrity: sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==} + engines: {node: '>=16.0.0'} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + restructure@3.0.2: + resolution: {integrity: sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==} + + retext-latin@4.0.0: + resolution: {integrity: sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==} + + retext-smartypants@6.2.0: + resolution: {integrity: sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==} + + retext-stringify@4.0.0: + resolution: {integrity: sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==} + + retext@9.0.0: + resolution: {integrity: sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==} + + rollup@4.43.0: + resolution: {integrity: sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shiki@3.6.0: + resolution: {integrity: sha512-tKn/Y0MGBTffQoklaATXmTqDU02zx8NYBGQ+F6gy87/YjKbizcLd+Cybh/0ZtOBX9r1NEnAy/GTRDKtOsc1L9w==} + + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + smol-toml@1.3.4: + resolution: {integrity: sha512-UOPtVuYkzYGee0Bd2Szz8d2G3RfMfJ2t3qVdZUAozZyAk+a0Sxa+QKix0YCwjL/A1RR0ar44nCxaoN9FxdJGwA==} + engines: {node: '>= 18'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + tsconfck@3.1.6: + resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + + ultrahtml@1.6.0: + resolution: {integrity: sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==} + + uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + + undici-types@7.8.0: + resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} + + unicode-properties@1.4.1: + resolution: {integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==} + + unicode-trie@2.0.0: + resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unifont@0.5.0: + resolution: {integrity: sha512-4DueXMP5Hy4n607sh+vJ+rajoLu778aU3GzqeTCqsD/EaUcvqZT9wPC8kgK6Vjh22ZskrxyRCR71FwNOaYn6jA==} + + unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + + unist-util-is@6.0.0: + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + + unist-util-modify-children@4.0.0: + resolution: {integrity: sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-remove-position@5.0.0: + resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-children@3.0.0: + resolution: {integrity: sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==} + + unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + + unstorage@1.16.0: + resolution: {integrity: sha512-WQ37/H5A7LcRPWfYOrDa1Ys02xAbpPJq6q5GkO88FBXVSQzHd7+BjEwfRqyaSWCv9MbsJy058GWjjPjcJ16GGA==} + peerDependencies: + '@azure/app-configuration': ^1.8.0 + '@azure/cosmos': ^4.2.0 + '@azure/data-tables': ^13.3.0 + '@azure/identity': ^4.6.0 + '@azure/keyvault-secrets': ^4.9.0 + '@azure/storage-blob': ^12.26.0 + '@capacitor/preferences': ^6.0.3 || ^7.0.0 + '@deno/kv': '>=0.9.0' + '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 + '@planetscale/database': ^1.19.0 + '@upstash/redis': ^1.34.3 + '@vercel/blob': '>=0.27.1' + '@vercel/kv': ^1.0.1 + aws4fetch: ^1.0.20 + db0: '>=0.2.1' + idb-keyval: ^6.2.1 + ioredis: ^5.4.2 + uploadthing: ^7.4.4 + peerDependenciesMeta: + '@azure/app-configuration': + optional: true + '@azure/cosmos': + optional: true + '@azure/data-tables': + optional: true + '@azure/identity': + optional: true + '@azure/keyvault-secrets': + optional: true + '@azure/storage-blob': + optional: true + '@capacitor/preferences': + optional: true + '@deno/kv': + optional: true + '@netlify/blobs': + optional: true + '@planetscale/database': + optional: true + '@upstash/redis': + optional: true + '@vercel/blob': + optional: true + '@vercel/kv': + optional: true + aws4fetch: + optional: true + db0: + optional: true + idb-keyval: + optional: true + ioredis: + optional: true + uploadthing: + optional: true + + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + + vfile-message@4.0.2: + resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite@6.3.5: + resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitefu@1.0.6: + resolution: {integrity: sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + vite: + optional: true + + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which-pm-runs@1.1.0: + resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==} + engines: {node: '>=4'} + + widest-line@5.0.0: + resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} + engines: {node: '>=18'} + + wrap-ansi@9.0.0: + resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} + engines: {node: '>=18'} + + xxhash-wasm@1.1.0: + resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yocto-queue@1.2.1: + resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==} + engines: {node: '>=12.20'} + + yocto-spinner@0.2.3: + resolution: {integrity: sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==} + engines: {node: '>=18.19'} + + yoctocolors@2.1.1: + resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==} + engines: {node: '>=18'} + + zod-to-json-schema@3.24.5: + resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==} + peerDependencies: + zod: ^3.24.1 + + zod-to-ts@1.2.0: + resolution: {integrity: sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==} + peerDependencies: + typescript: ^4.9.4 || ^5.0.2 + zod: ^3 + + zod@3.25.64: + resolution: {integrity: sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g==} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@astrojs/compiler@2.12.2': {} + + '@astrojs/internal-helpers@0.6.1': {} + + '@astrojs/markdown-remark@6.3.2': + dependencies: + '@astrojs/internal-helpers': 0.6.1 + '@astrojs/prism': 3.3.0 + github-slugger: 2.0.0 + hast-util-from-html: 2.0.3 + hast-util-to-text: 4.0.2 + import-meta-resolve: 4.1.0 + js-yaml: 4.1.0 + mdast-util-definitions: 6.0.0 + rehype-raw: 7.0.0 + rehype-stringify: 10.0.1 + remark-gfm: 4.0.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + remark-smartypants: 3.0.2 + shiki: 3.6.0 + smol-toml: 1.3.4 + unified: 11.0.5 + unist-util-remove-position: 5.0.0 + unist-util-visit: 5.0.0 + unist-util-visit-parents: 6.0.1 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@astrojs/prism@3.3.0': + dependencies: + prismjs: 1.30.0 + + '@astrojs/telemetry@3.3.0': + dependencies: + ci-info: 4.2.0 + debug: 4.4.1 + dlv: 1.1.3 + dset: 3.1.4 + is-docker: 3.0.0 + is-wsl: 3.1.0 + which-pm-runs: 1.1.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/parser@7.27.5': + dependencies: + '@babel/types': 7.27.6 + + '@babel/types@7.27.6': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@cacheable/node-cache@1.5.6': + dependencies: + cacheable: 1.10.0 + hookified: 1.9.1 + keyv: 5.3.4 + + '@capsizecss/unpack@2.4.0': + dependencies: + blob-to-buffer: 1.2.9 + cross-fetch: 3.2.0 + fontkit: 2.0.4 + transitivePeerDependencies: + - encoding + + '@emnapi/runtime@1.4.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.25.5': + optional: true + + '@esbuild/android-arm64@0.25.5': + optional: true + + '@esbuild/android-arm@0.25.5': + optional: true + + '@esbuild/android-x64@0.25.5': + optional: true + + '@esbuild/darwin-arm64@0.25.5': + optional: true + + '@esbuild/darwin-x64@0.25.5': + optional: true + + '@esbuild/freebsd-arm64@0.25.5': + optional: true + + '@esbuild/freebsd-x64@0.25.5': + optional: true + + '@esbuild/linux-arm64@0.25.5': + optional: true + + '@esbuild/linux-arm@0.25.5': + optional: true + + '@esbuild/linux-ia32@0.25.5': + optional: true + + '@esbuild/linux-loong64@0.25.5': + optional: true + + '@esbuild/linux-mips64el@0.25.5': + optional: true + + '@esbuild/linux-ppc64@0.25.5': + optional: true + + '@esbuild/linux-riscv64@0.25.5': + optional: true + + '@esbuild/linux-s390x@0.25.5': + optional: true + + '@esbuild/linux-x64@0.25.5': + optional: true + + '@esbuild/netbsd-arm64@0.25.5': + optional: true + + '@esbuild/netbsd-x64@0.25.5': + optional: true + + '@esbuild/openbsd-arm64@0.25.5': + optional: true + + '@esbuild/openbsd-x64@0.25.5': + optional: true + + '@esbuild/sunos-x64@0.25.5': + optional: true + + '@esbuild/win32-arm64@0.25.5': + optional: true + + '@esbuild/win32-ia32@0.25.5': + optional: true + + '@esbuild/win32-x64@0.25.5': + optional: true + + '@img/sharp-darwin-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.4 + optional: true + + '@img/sharp-darwin-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.0.5': + optional: true + + '@img/sharp-libvips-linux-s390x@1.0.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + optional: true + + '@img/sharp-linux-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.4 + optional: true + + '@img/sharp-linux-arm@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.5 + optional: true + + '@img/sharp-linux-s390x@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.0.4 + optional: true + + '@img/sharp-linux-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + optional: true + + '@img/sharp-wasm32@0.33.5': + dependencies: + '@emnapi/runtime': 1.4.3 + optional: true + + '@img/sharp-win32-ia32@0.33.5': + optional: true + + '@img/sharp-win32-x64@0.33.5': + optional: true + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@keyv/serialize@1.0.3': + dependencies: + buffer: 6.0.3 + + '@oslojs/encoding@1.1.0': {} + + '@rollup/pluginutils@5.1.4(rollup@4.43.0)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.2 + optionalDependencies: + rollup: 4.43.0 + + '@rollup/rollup-android-arm-eabi@4.43.0': + optional: true + + '@rollup/rollup-android-arm64@4.43.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.43.0': + optional: true + + '@rollup/rollup-darwin-x64@4.43.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.43.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.43.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.43.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.43.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.43.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.43.0': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.43.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.43.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.43.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.43.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.43.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.43.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.43.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.43.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.43.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.43.0': + optional: true + + '@shikijs/core@3.6.0': + dependencies: + '@shikijs/types': 3.6.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@3.6.0': + dependencies: + '@shikijs/types': 3.6.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.3 + + '@shikijs/engine-oniguruma@3.6.0': + dependencies: + '@shikijs/types': 3.6.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@3.6.0': + dependencies: + '@shikijs/types': 3.6.0 + + '@shikijs/themes@3.6.0': + dependencies: + '@shikijs/types': 3.6.0 + + '@shikijs/types@3.6.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@swc/helpers@0.5.17': + dependencies: + tslib: 2.8.1 + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + + '@types/estree@1.0.7': {} + + '@types/estree@1.0.8': {} + + '@types/fontkit@2.0.8': + dependencies: + '@types/node': 24.0.1 + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/ms@2.1.0': {} + + '@types/nlcst@2.0.3': + dependencies: + '@types/unist': 3.0.3 + + '@types/node@24.0.1': + dependencies: + undici-types: 7.8.0 + + '@types/unist@3.0.3': {} + + '@ungap/structured-clone@1.3.0': {} + + acorn@8.15.0: {} + + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@6.2.1: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + argparse@2.0.1: {} + + aria-query@5.3.2: {} + + array-iterate@2.0.1: {} + + astro@5.9.3(@types/node@24.0.1)(rollup@4.43.0)(typescript@5.8.3): + dependencies: + '@astrojs/compiler': 2.12.2 + '@astrojs/internal-helpers': 0.6.1 + '@astrojs/markdown-remark': 6.3.2 + '@astrojs/telemetry': 3.3.0 + '@capsizecss/unpack': 2.4.0 + '@oslojs/encoding': 1.1.0 + '@rollup/pluginutils': 5.1.4(rollup@4.43.0) + acorn: 8.15.0 + aria-query: 5.3.2 + axobject-query: 4.1.0 + boxen: 8.0.1 + ci-info: 4.2.0 + clsx: 2.1.1 + common-ancestor-path: 1.0.1 + cookie: 1.0.2 + cssesc: 3.0.0 + debug: 4.4.1 + deterministic-object-hash: 2.0.2 + devalue: 5.1.1 + diff: 5.2.0 + dlv: 1.1.3 + dset: 3.1.4 + es-module-lexer: 1.7.0 + esbuild: 0.25.5 + estree-walker: 3.0.3 + flattie: 1.1.1 + fontace: 0.3.0 + github-slugger: 2.0.0 + html-escaper: 3.0.3 + http-cache-semantics: 4.2.0 + import-meta-resolve: 4.1.0 + js-yaml: 4.1.0 + kleur: 4.1.5 + magic-string: 0.30.17 + magicast: 0.3.5 + mrmime: 2.0.1 + neotraverse: 0.6.18 + p-limit: 6.2.0 + p-queue: 8.1.0 + package-manager-detector: 1.3.0 + picomatch: 4.0.2 + prompts: 2.4.2 + rehype: 13.0.2 + semver: 7.7.2 + shiki: 3.6.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.14 + tsconfck: 3.1.6(typescript@5.8.3) + ultrahtml: 1.6.0 + unifont: 0.5.0 + unist-util-visit: 5.0.0 + unstorage: 1.16.0 + vfile: 6.0.3 + vite: 6.3.5(@types/node@24.0.1) + vitefu: 1.0.6(vite@6.3.5(@types/node@24.0.1)) + xxhash-wasm: 1.1.0 + yargs-parser: 21.1.1 + yocto-spinner: 0.2.3 + zod: 3.25.64 + zod-to-json-schema: 3.24.5(zod@3.25.64) + zod-to-ts: 1.2.0(typescript@5.8.3)(zod@3.25.64) + optionalDependencies: + sharp: 0.33.5 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@netlify/blobs' + - '@planetscale/database' + - '@types/node' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/kv' + - aws4fetch + - db0 + - encoding + - idb-keyval + - ioredis + - jiti + - less + - lightningcss + - rollup + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - typescript + - uploadthing + - yaml + + axobject-query@4.1.0: {} + + bail@2.0.2: {} + + base-64@1.0.0: {} + + base64-js@1.5.1: {} + + blob-to-buffer@1.2.9: {} + + boxen@8.0.1: + dependencies: + ansi-align: 3.0.1 + camelcase: 8.0.0 + chalk: 5.4.1 + cli-boxes: 3.0.0 + string-width: 7.2.0 + type-fest: 4.41.0 + widest-line: 5.0.0 + wrap-ansi: 9.0.0 + + brotli@1.3.3: + dependencies: + base64-js: 1.5.1 + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + cacheable@1.10.0: + dependencies: + hookified: 1.9.1 + keyv: 5.3.4 + + camelcase@8.0.0: {} + + ccount@2.0.1: {} + + chalk@5.4.1: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + ci-info@4.2.0: {} + + cli-boxes@3.0.0: {} + + clone@2.1.2: {} + + clsx@2.1.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + optional: true + + color-name@1.1.4: + optional: true + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + optional: true + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + optional: true + + comma-separated-tokens@2.0.3: {} + + common-ancestor-path@1.0.1: {} + + cookie-es@1.2.2: {} + + cookie@1.0.2: {} + + cross-fetch@3.2.0: + dependencies: + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + + crossws@0.3.5: + dependencies: + uncrypto: 0.1.3 + + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + + cssesc@3.0.0: {} + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.2.0: + dependencies: + character-entities: 2.0.2 + + defu@6.1.4: {} + + dequal@2.0.3: {} + + destr@2.0.5: {} + + detect-libc@2.0.4: + optional: true + + deterministic-object-hash@2.0.2: + dependencies: + base-64: 1.0.0 + + devalue@5.1.1: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + dfa@1.2.0: {} + + diff@5.2.0: {} + + dlv@1.1.3: {} + + dset@3.1.4: {} + + emoji-regex@10.4.0: {} + + emoji-regex@8.0.0: {} + + entities@6.0.1: {} + + es-module-lexer@1.7.0: {} + + esbuild@0.25.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.5 + '@esbuild/android-arm': 0.25.5 + '@esbuild/android-arm64': 0.25.5 + '@esbuild/android-x64': 0.25.5 + '@esbuild/darwin-arm64': 0.25.5 + '@esbuild/darwin-x64': 0.25.5 + '@esbuild/freebsd-arm64': 0.25.5 + '@esbuild/freebsd-x64': 0.25.5 + '@esbuild/linux-arm': 0.25.5 + '@esbuild/linux-arm64': 0.25.5 + '@esbuild/linux-ia32': 0.25.5 + '@esbuild/linux-loong64': 0.25.5 + '@esbuild/linux-mips64el': 0.25.5 + '@esbuild/linux-ppc64': 0.25.5 + '@esbuild/linux-riscv64': 0.25.5 + '@esbuild/linux-s390x': 0.25.5 + '@esbuild/linux-x64': 0.25.5 + '@esbuild/netbsd-arm64': 0.25.5 + '@esbuild/netbsd-x64': 0.25.5 + '@esbuild/openbsd-arm64': 0.25.5 + '@esbuild/openbsd-x64': 0.25.5 + '@esbuild/sunos-x64': 0.25.5 + '@esbuild/win32-arm64': 0.25.5 + '@esbuild/win32-ia32': 0.25.5 + '@esbuild/win32-x64': 0.25.5 + + escape-string-regexp@5.0.0: {} + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + eventemitter3@5.0.1: {} + + extend@3.0.2: {} + + fast-deep-equal@3.1.3: {} + + fdir@6.4.6(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + + flattie@1.1.1: {} + + fontace@0.3.0: + dependencies: + '@types/fontkit': 2.0.8 + fontkit: 2.0.4 + + fontkit@2.0.4: + dependencies: + '@swc/helpers': 0.5.17 + brotli: 1.3.3 + clone: 2.1.2 + dfa: 1.2.0 + fast-deep-equal: 3.1.3 + restructure: 3.0.2 + tiny-inflate: 1.0.3 + unicode-properties: 1.4.1 + unicode-trie: 2.0.0 + + fsevents@2.3.3: + optional: true + + get-east-asian-width@1.3.0: {} + + github-slugger@2.0.0: {} + + h3@1.15.3: + dependencies: + cookie-es: 1.2.2 + crossws: 0.3.5 + defu: 6.1.4 + destr: 2.0.5 + iron-webcrypto: 1.2.1 + node-mock-http: 1.0.0 + radix3: 1.1.2 + ufo: 1.6.1 + uncrypto: 0.1.3 + + hast-util-from-html@2.0.3: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.3.0 + vfile: 6.0.3 + vfile-message: 4.0.2 + + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.1.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-raw@9.1.0: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + '@ungap/structured-clone': 1.3.0 + hast-util-from-parse5: 8.0.3 + hast-util-to-parse5: 8.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.0 + parse5: 7.3.0 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-to-parse5@8.0.0: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + property-information: 6.5.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-text@4.0.2: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + + hookified@1.9.1: {} + + html-escaper@3.0.3: {} + + html-void-elements@3.0.0: {} + + http-cache-semantics@4.2.0: {} + + ieee754@1.2.1: {} + + import-meta-resolve@4.1.0: {} + + iron-webcrypto@1.2.1: {} + + is-arrayish@0.3.2: + optional: true + + is-docker@3.0.0: {} + + is-fullwidth-code-point@3.0.0: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-plain-obj@4.1.0: {} + + is-wsl@3.1.0: + dependencies: + is-inside-container: 1.0.0 + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + keyv@5.3.4: + dependencies: + '@keyv/serialize': 1.0.3 + + kleur@3.0.3: {} + + kleur@4.1.5: {} + + longest-streak@3.1.0: {} + + lru-cache@10.4.3: {} + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + magicast@0.3.5: + dependencies: + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 + source-map-js: 1.2.1 + + markdown-table@3.0.4: {} + + mdast-util-definitions@6.0.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + unist-util-visit: 5.0.0 + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.0 + + mdast-util-to-hast@13.2.0: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + mdn-data@2.12.2: {} + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.2.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.1 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + neotraverse@0.6.18: {} + + nlcst-to-string@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + + node-fetch-native@1.6.6: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-mock-http@1.0.0: {} + + normalize-path@3.0.0: {} + + ofetch@1.4.1: + dependencies: + destr: 2.0.5 + node-fetch-native: 1.6.6 + ufo: 1.6.1 + + ohash@2.0.11: {} + + oniguruma-parser@0.12.1: {} + + oniguruma-to-es@4.3.3: + dependencies: + oniguruma-parser: 0.12.1 + regex: 6.0.1 + regex-recursion: 6.0.2 + + p-limit@6.2.0: + dependencies: + yocto-queue: 1.2.1 + + p-queue@8.1.0: + dependencies: + eventemitter3: 5.0.1 + p-timeout: 6.1.4 + + p-timeout@6.1.4: {} + + package-manager-detector@1.3.0: {} + + pako@0.2.9: {} + + parse-latin@7.0.0: + dependencies: + '@types/nlcst': 2.0.3 + '@types/unist': 3.0.3 + nlcst-to-string: 4.0.0 + unist-util-modify-children: 4.0.0 + unist-util-visit-children: 3.0.0 + vfile: 6.0.3 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.2: {} + + postcss@8.5.5: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prismjs@1.30.0: {} + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + property-information@6.5.0: {} + + property-information@7.1.0: {} + + radix3@1.1.2: {} + + readdirp@4.1.2: {} + + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.0.1: + dependencies: + regex-utilities: 2.3.0 + + rehype-parse@9.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-from-html: 2.0.3 + unified: 11.0.5 + + rehype-raw@7.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-raw: 9.1.0 + vfile: 6.0.3 + + rehype-stringify@10.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + unified: 11.0.5 + + rehype@13.0.2: + dependencies: + '@types/hast': 3.0.4 + rehype-parse: 9.0.1 + rehype-stringify: 10.0.1 + unified: 11.0.5 + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.0 + unified: 11.0.5 + vfile: 6.0.3 + + remark-smartypants@3.0.2: + dependencies: + retext: 9.0.0 + retext-smartypants: 6.2.0 + unified: 11.0.5 + unist-util-visit: 5.0.0 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + restructure@3.0.2: {} + + retext-latin@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + parse-latin: 7.0.0 + unified: 11.0.5 + + retext-smartypants@6.2.0: + dependencies: + '@types/nlcst': 2.0.3 + nlcst-to-string: 4.0.0 + unist-util-visit: 5.0.0 + + retext-stringify@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + nlcst-to-string: 4.0.0 + unified: 11.0.5 + + retext@9.0.0: + dependencies: + '@types/nlcst': 2.0.3 + retext-latin: 4.0.0 + retext-stringify: 4.0.0 + unified: 11.0.5 + + rollup@4.43.0: + dependencies: + '@types/estree': 1.0.7 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.43.0 + '@rollup/rollup-android-arm64': 4.43.0 + '@rollup/rollup-darwin-arm64': 4.43.0 + '@rollup/rollup-darwin-x64': 4.43.0 + '@rollup/rollup-freebsd-arm64': 4.43.0 + '@rollup/rollup-freebsd-x64': 4.43.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.43.0 + '@rollup/rollup-linux-arm-musleabihf': 4.43.0 + '@rollup/rollup-linux-arm64-gnu': 4.43.0 + '@rollup/rollup-linux-arm64-musl': 4.43.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.43.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.43.0 + '@rollup/rollup-linux-riscv64-gnu': 4.43.0 + '@rollup/rollup-linux-riscv64-musl': 4.43.0 + '@rollup/rollup-linux-s390x-gnu': 4.43.0 + '@rollup/rollup-linux-x64-gnu': 4.43.0 + '@rollup/rollup-linux-x64-musl': 4.43.0 + '@rollup/rollup-win32-arm64-msvc': 4.43.0 + '@rollup/rollup-win32-ia32-msvc': 4.43.0 + '@rollup/rollup-win32-x64-msvc': 4.43.0 + fsevents: 2.3.3 + + semver@7.7.2: {} + + sharp@0.33.5: + dependencies: + color: 4.2.3 + detect-libc: 2.0.4 + semver: 7.7.2 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 + optional: true + + shiki@3.6.0: + dependencies: + '@shikijs/core': 3.6.0 + '@shikijs/engine-javascript': 3.6.0 + '@shikijs/engine-oniguruma': 3.6.0 + '@shikijs/langs': 3.6.0 + '@shikijs/themes': 3.6.0 + '@shikijs/types': 3.6.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + optional: true + + sisteransi@1.0.5: {} + + smol-toml@1.3.4: {} + + source-map-js@1.2.1: {} + + space-separated-tokens@2.0.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.4.0 + get-east-asian-width: 1.3.0 + strip-ansi: 7.1.0 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + tiny-inflate@1.0.3: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.14: + dependencies: + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + + tr46@0.0.3: {} + + trim-lines@3.0.1: {} + + trough@2.2.0: {} + + tsconfck@3.1.6(typescript@5.8.3): + optionalDependencies: + typescript: 5.8.3 + + tslib@2.8.1: {} + + type-fest@4.41.0: {} + + typescript@5.8.3: {} + + ufo@1.6.1: {} + + ultrahtml@1.6.0: {} + + uncrypto@0.1.3: {} + + undici-types@7.8.0: {} + + unicode-properties@1.4.1: + dependencies: + base64-js: 1.5.1 + unicode-trie: 2.0.0 + + unicode-trie@2.0.0: + dependencies: + pako: 0.2.9 + tiny-inflate: 1.0.3 + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unifont@0.5.0: + dependencies: + css-tree: 3.1.0 + ohash: 2.0.11 + + unist-util-find-after@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + + unist-util-is@6.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-modify-children@4.0.0: + dependencies: + '@types/unist': 3.0.3 + array-iterate: 2.0.1 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-remove-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-visit: 5.0.0 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-children@3.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.1: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + unstorage@1.16.0: + dependencies: + anymatch: 3.1.3 + chokidar: 4.0.3 + destr: 2.0.5 + h3: 1.15.3 + lru-cache: 10.4.3 + node-fetch-native: 1.6.6 + ofetch: 1.4.1 + ufo: 1.6.1 + + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + + vfile-message@4.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.2 + + vite@6.3.5(@types/node@24.0.1): + dependencies: + esbuild: 0.25.5 + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + postcss: 8.5.5 + rollup: 4.43.0 + tinyglobby: 0.2.14 + optionalDependencies: + '@types/node': 24.0.1 + fsevents: 2.3.3 + + vitefu@1.0.6(vite@6.3.5(@types/node@24.0.1)): + optionalDependencies: + vite: 6.3.5(@types/node@24.0.1) + + web-namespaces@2.0.1: {} + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which-pm-runs@1.1.0: {} + + widest-line@5.0.0: + dependencies: + string-width: 7.2.0 + + wrap-ansi@9.0.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 7.2.0 + strip-ansi: 7.1.0 + + xxhash-wasm@1.1.0: {} + + yargs-parser@21.1.1: {} + + yocto-queue@1.2.1: {} + + yocto-spinner@0.2.3: + dependencies: + yoctocolors: 2.1.1 + + yoctocolors@2.1.1: {} + + zod-to-json-schema@3.24.5(zod@3.25.64): + dependencies: + zod: 3.25.64 + + zod-to-ts@1.2.0(typescript@5.8.3)(zod@3.25.64): + dependencies: + typescript: 5.8.3 + zod: 3.25.64 + + zod@3.25.64: {} + + zwitch@2.0.4: {} diff --git a/servers/public/favicon.svg b/servers/public/favicon.svg new file mode 100644 index 00000000..fbf392b3 --- /dev/null +++ b/servers/public/favicon.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/servers/src/assets/astro.svg b/servers/src/assets/astro.svg new file mode 100644 index 00000000..8cf8fb0c --- /dev/null +++ b/servers/src/assets/astro.svg @@ -0,0 +1 @@ + diff --git a/servers/src/assets/background.svg b/servers/src/assets/background.svg new file mode 100644 index 00000000..4b2be0ac --- /dev/null +++ b/servers/src/assets/background.svg @@ -0,0 +1 @@ + diff --git a/servers/src/components/ServerEntry.astro b/servers/src/components/ServerEntry.astro new file mode 100644 index 00000000..515c7bca --- /dev/null +++ b/servers/src/components/ServerEntry.astro @@ -0,0 +1,21 @@ +--- +import { fetchServerStatus } from "../scripts/servers/info"; +import { Server } from "../scripts/servers/utils"; + +interface Props { + server: Server; +} + +const { server } = Astro.props; +const status = await fetchServerStatus(server); +--- + +
  • + {server.address} + {server.secure ? "🔒" : "🔓"} + {JSON.stringify(status)} +
  • diff --git a/servers/src/data/servers.json b/servers/src/data/servers.json new file mode 100644 index 00000000..421ad1ad --- /dev/null +++ b/servers/src/data/servers.json @@ -0,0 +1,3 @@ +[ + {"address": "127.0.0.1", "secure": false} +] \ No newline at end of file diff --git a/servers/src/layouts/Layout.astro b/servers/src/layouts/Layout.astro new file mode 100644 index 00000000..fc2e13eb --- /dev/null +++ b/servers/src/layouts/Layout.astro @@ -0,0 +1,21 @@ + + + + + + + Setonix Serverlist + + + + + + + diff --git a/servers/src/pages/data.json.ts b/servers/src/pages/data.json.ts new file mode 100644 index 00000000..c5a57e7e --- /dev/null +++ b/servers/src/pages/data.json.ts @@ -0,0 +1,10 @@ +import type { APIRoute } from 'astro'; +import { loadServersFromConfig } from '../scripts/servers/list'; + +export async function GET() { + const servers = await loadServersFromConfig(); + return new Response(JSON.stringify(servers), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); +}; \ No newline at end of file diff --git a/servers/src/pages/index.astro b/servers/src/pages/index.astro new file mode 100644 index 00000000..c068ddd3 --- /dev/null +++ b/servers/src/pages/index.astro @@ -0,0 +1,23 @@ +--- +import Layout from '../layouts/Layout.astro'; +import { loadServersFromConfig } from '../scripts/servers/list'; +import { fetchAllServerStatus} from '../scripts/servers/info'; +import ServerEntry from '../components/ServerEntry.astro'; + +const servers = await loadServersFromConfig(); +--- + + +

    Server List

    +
      + + {servers.map((e) => +
    • + {e.address} {e.secure ? "🔒" : "🔓"} (Loading...) +
    • +
      )} +
    +
    diff --git a/servers/src/scripts/servers/info.ts b/servers/src/scripts/servers/info.ts new file mode 100644 index 00000000..4eba1a02 --- /dev/null +++ b/servers/src/scripts/servers/info.ts @@ -0,0 +1,43 @@ +import NodeCache from "@cacheable/node-cache"; +import { buildServerHttpUrl, type Server } from "./utils"; + +const cache = new NodeCache({ stdTTL: 300 }); + +export type ServerStatus = { + description: string; + maxPlayers?: number; + currentPlayers: number; + packsSignature: Record; +} + +export async function fetchServerStatus(server: Server) : Promise { + const cacheKey = `server-status:${server.address}`; + const cached = cache.get(cacheKey); + if (cached) { + return Promise.resolve(cached as ServerStatus); + } + const url = buildServerHttpUrl(server); + const response = await fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + "X-Setonix-Method": "info" + }, + }) + .then((res) => { + if (!res.ok) { + throw new Error(`Server ${server.address} returned status ${res.status}`); + } + return res.json(); + }) + .catch((error) => { + console.error(`Error fetching status for server ${server.address}:`, error); + return null; + }) as ServerStatus | null; + if (!response) { + console.warn(`Invalid response from server ${server.address}:`, response); + return null; + } + cache.set(cacheKey, response); + return response; +} diff --git a/servers/src/scripts/servers/list.ts b/servers/src/scripts/servers/list.ts new file mode 100644 index 00000000..f9090203 --- /dev/null +++ b/servers/src/scripts/servers/list.ts @@ -0,0 +1,58 @@ +import {NodeCache} from '@cacheable/node-cache'; +import { promises as fs } from 'fs'; +import type { Server } from './utils'; +import { REMOTE_URLS } from "astro:env/server"; + +// Cache with 5 minutes TTL +const cache = new NodeCache({ stdTTL: 300 }); +// Cache for local servers list +let localCache: Server[] | null = null; + +/** + * Loads the local servers.json and merges it with all remote lists. + */ + export async function loadServers( + localPath: string, + remoteUrls: string[] = [] + ): Promise { + if (!localCache) { + const localData = await fs.readFile(localPath, 'utf-8'); + localCache = JSON.parse(localData); + } + const local = localCache!; + + async function fetchRemote(url: string): Promise { + const cacheKey = `servers:${url}`; + const cached = cache.get(cacheKey); + if (cached) return cached; + + const res = await fetch(url); + console.log(`Fetching remote servers from ${url}`); + if (!res.ok) { + console.warn(`Fetch from ${url} failed: ${res.status}`); + return []; + } + + const data = await res.json(); + if (!('servers' in data)) { + console.warn(`Invalid data format from ${url}:`, data); + return []; + } + const servers = data['servers'] as Server[]; + cache.set(cacheKey, servers); + return servers; + } + + const remoteLists = await Promise.all(remoteUrls.map(fetchRemote)); + + return [...local, ...remoteLists.flat()]; +} + +export async function loadServersFromConfig() { + const localPath = 'src/data/servers.json'; + const remotes = REMOTE_URLS?.split(','); + return await loadServers( + localPath, + remotes?.filter(url => url.trim() !== '') || [] + ); +} diff --git a/servers/src/scripts/servers/utils.ts b/servers/src/scripts/servers/utils.ts new file mode 100644 index 00000000..cda3651f --- /dev/null +++ b/servers/src/scripts/servers/utils.ts @@ -0,0 +1,18 @@ +import { z } from "astro:content"; + +export const ServerObject = z.object({ + address: z.string().url(), + secure: z.boolean().optional(), + highlighted: z.boolean().optional(), +}); + +export type Server = z.infer; + +export function buildServerHttpUrl(server: Server): string { + console.log(`Fetching status for server ${JSON.stringify(server)}`); + const protocol = server.secure ? "https" : "http"; + const [hostPort, ...segments] = server.address.split("/"); + const path = segments.length ? `/${segments.join("/")}` : ""; + const hostWithPort = hostPort.includes(":") ? hostPort : `${hostPort}:28006`; + return `${protocol}://${hostWithPort}${path}`; +} diff --git a/servers/tsconfig.json b/servers/tsconfig.json new file mode 100644 index 00000000..8bf91d3b --- /dev/null +++ b/servers/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "astro/tsconfigs/strict", + "include": [".astro/types.d.ts", "**/*"], + "exclude": ["dist"] +} From f58edab57781827f8ad36fe9d6ad04b9d87a0142 Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Mon, 16 Jun 2025 13:34:31 +0200 Subject: [PATCH 11/18] Style server list and add join url --- app/lib/bloc/multiplayer.mapper.dart | 5 ++ servers/package.json | 5 +- servers/pnpm-lock.yaml | 8 +++ .../src/components/LoadingServerEntry.astro | 16 +++++ servers/src/components/ServerEntry.astro | 64 +++++++++++++++---- servers/src/layouts/Layout.astro | 3 + servers/src/pages/index.astro | 44 ++++++++----- servers/src/scripts/servers/info.ts | 9 +-- servers/src/scripts/servers/list.ts | 2 +- servers/src/scripts/servers/utils.ts | 5 +- 10 files changed, 124 insertions(+), 37 deletions(-) create mode 100644 servers/src/components/LoadingServerEntry.astro diff --git a/app/lib/bloc/multiplayer.mapper.dart b/app/lib/bloc/multiplayer.mapper.dart index 3908cbcd..c80fa04c 100644 --- a/app/lib/bloc/multiplayer.mapper.dart +++ b/app/lib/bloc/multiplayer.mapper.dart @@ -445,11 +445,16 @@ class MultiplayerConnectedStateMapper v.pipe; static const Field> _f$pipe = Field('pipe', _$pipe); + static UserManager _$userManager(MultiplayerConnectedState v) => + v.userManager; + static const Field _f$userManager = + Field('userManager', _$userManager, mode: FieldMode.member); @override final MappableFields fields = const { #networker: _f$networker, #pipe: _f$pipe, + #userManager: _f$userManager, }; static MultiplayerConnectedState _instantiate(DecodingData data) { diff --git a/servers/package.json b/servers/package.json index 458ce235..d97ef6a5 100644 --- a/servers/package.json +++ b/servers/package.json @@ -10,7 +10,8 @@ }, "packageManager": "pnpm@10.12.1", "dependencies": { - "astro": "^5.9.3", - "@cacheable/node-cache": "^1.5.6" + "@cacheable/node-cache": "^1.5.6", + "@linwooddev/style": "^0.4.2", + "astro": "^5.9.3" } } \ No newline at end of file diff --git a/servers/pnpm-lock.yaml b/servers/pnpm-lock.yaml index 3fc12248..0906b2d0 100644 --- a/servers/pnpm-lock.yaml +++ b/servers/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@cacheable/node-cache': specifier: ^1.5.6 version: 1.5.6 + '@linwooddev/style': + specifier: ^0.4.2 + version: 0.4.2 astro: specifier: ^5.9.3 version: 5.9.3(@types/node@24.0.1)(rollup@4.43.0)(typescript@5.8.3) @@ -321,6 +324,9 @@ packages: '@keyv/serialize@1.0.3': resolution: {integrity: sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g==} + '@linwooddev/style@0.4.2': + resolution: {integrity: sha512-ZYDdLnl7D8ttJ9lA3BdZtBw7pCkfwF3EEJ3bCmTibP/10ESQl+RPEUbmKbihudYA+6bX65bZIr4Yaimyh7i15w==} + '@oslojs/encoding@1.1.0': resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} @@ -1708,6 +1714,8 @@ snapshots: dependencies: buffer: 6.0.3 + '@linwooddev/style@0.4.2': {} + '@oslojs/encoding@1.1.0': {} '@rollup/pluginutils@5.1.4(rollup@4.43.0)': diff --git a/servers/src/components/LoadingServerEntry.astro b/servers/src/components/LoadingServerEntry.astro new file mode 100644 index 00000000..0a2fe6dd --- /dev/null +++ b/servers/src/components/LoadingServerEntry.astro @@ -0,0 +1,16 @@ +--- +import { fetchServerStatus } from "../scripts/servers/info"; +import type { Server } from "../scripts/servers/utils"; +import ServerEntry from "./ServerEntry.astro"; + + +interface Props { + server: Server; +} + +const { server } = Astro.props; +--- + + + + diff --git a/servers/src/components/ServerEntry.astro b/servers/src/components/ServerEntry.astro index 515c7bca..f5455e57 100644 --- a/servers/src/components/ServerEntry.astro +++ b/servers/src/components/ServerEntry.astro @@ -1,21 +1,63 @@ --- -import { fetchServerStatus } from "../scripts/servers/info"; -import { Server } from "../scripts/servers/utils"; +import { fetchServerStatus, type ServerStatus } from "../scripts/servers/info"; +import type { Server } from "../scripts/servers/utils"; interface Props { server: Server; + serverStatus?: boolean; } const { server } = Astro.props; -const status = await fetchServerStatus(server); +let serverStatus: ServerStatus | undefined | null = undefined; +if (Astro.props.serverStatus) { + serverStatus = await fetchServerStatus(server); +} + +function buildJoinURL(): string { + return `https://launch.setonix.linwood.dev/connect?address=${encodeURIComponent(server.address)}&secure=${encodeURIComponent(server.secure ?? 'true')}`; +} + +const secureLabel = server.secure ? "Secure server" : "Unsecure server"; +const playerText = serverStatus == null + ? "Offline" + : serverStatus.currentPlayers + + (serverStatus.maxPlayers == null ? "" : `/${serverStatus.maxPlayers}`); --- -
  • - {server.address} - {server.secure ? "🔒" : "🔓"} - {JSON.stringify(status)} +
  • + +
    +

    {server.address}

    +
    + {serverStatus !== undefined && ( +

    + {playerText} +

    + )} + + {server.secure ? "🔒" : "🔓"} + +
    +
    + +

    + {serverStatus === undefined + ? "Loading…" + : serverStatus?.description || "No description available."} +

    +
  • diff --git a/servers/src/layouts/Layout.astro b/servers/src/layouts/Layout.astro index fc2e13eb..6fd6384e 100644 --- a/servers/src/layouts/Layout.astro +++ b/servers/src/layouts/Layout.astro @@ -1,3 +1,6 @@ +--- +import '@linwooddev/style/css/main.css'; +--- diff --git a/servers/src/pages/index.astro b/servers/src/pages/index.astro index c068ddd3..ff13cfac 100644 --- a/servers/src/pages/index.astro +++ b/servers/src/pages/index.astro @@ -1,23 +1,35 @@ --- -import Layout from '../layouts/Layout.astro'; -import { loadServersFromConfig } from '../scripts/servers/list'; -import { fetchAllServerStatus} from '../scripts/servers/info'; -import ServerEntry from '../components/ServerEntry.astro'; +import Layout from "../layouts/Layout.astro"; +import { loadServersFromConfig } from "../scripts/servers/list"; +import ServerEntry from "../components/ServerEntry.astro"; +import LoadingServerEntry from "../components/LoadingServerEntry.astro"; const servers = await loadServersFromConfig(); --- -

    Server List

    -
      - - {servers.map((e) => -
    • - {e.address} {e.secure ? "🔒" : "🔓"} (Loading...) -
    • -
      )} -
    + +
    +
    +

    Server List

    +
    +
    +
      + { + servers.map((e) => ( + + )) + } +
    +
    +
    diff --git a/servers/src/scripts/servers/info.ts b/servers/src/scripts/servers/info.ts index 4eba1a02..d9d3966c 100644 --- a/servers/src/scripts/servers/info.ts +++ b/servers/src/scripts/servers/info.ts @@ -1,7 +1,8 @@ import NodeCache from "@cacheable/node-cache"; -import { buildServerHttpUrl, type Server } from "./utils"; +import { buildServerURL, type Server } from "./utils"; -const cache = new NodeCache({ stdTTL: 300 }); +const cache = new NodeCache({ stdTTL: 60 * 10 }); +const failedTTL = 60 * 5; // 5 minutes for failed requests export type ServerStatus = { description: string; @@ -16,7 +17,7 @@ export async function fetchServerStatus(server: Server) : Promise; -export function buildServerHttpUrl(server: Server): string { - console.log(`Fetching status for server ${JSON.stringify(server)}`); - const protocol = server.secure ? "https" : "http"; +export function buildServerURL(server: Server, webSocket?: boolean): string { + const protocol = (webSocket ? "ws": "http") + (server.secure ? "s" : ""); const [hostPort, ...segments] = server.address.split("/"); const path = segments.length ? `/${segments.join("/")}` : ""; const hostWithPort = hostPort.includes(":") ? hostPort : `${hostPort}:28006`; From 1cb79de2e18098403ae7c1ab33627aa7c4a0a3df Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Mon, 16 Jun 2025 13:47:54 +0200 Subject: [PATCH 12/18] Add server list settings --- api/lib/src/models/server.dart | 5 +- app/lib/bloc/settings.dart | 24 ++++++++ app/lib/bloc/settings.mapper.dart | 33 ++++++++-- app/lib/l10n/app_en.arb | 4 +- app/lib/main.dart | 10 ++++ app/lib/pages/settings/home.dart | 7 ++- app/lib/pages/settings/servers.dart | 93 +++++++++++++++++++++++++++++ 7 files changed, 165 insertions(+), 11 deletions(-) create mode 100644 app/lib/pages/settings/servers.dart diff --git a/api/lib/src/models/server.dart b/api/lib/src/models/server.dart index 042c7716..4eb52384 100644 --- a/api/lib/src/models/server.dart +++ b/api/lib/src/models/server.dart @@ -53,10 +53,7 @@ Uri buildServerAddress(Uri uri, bool secure, {bool webSockets = true}) { host: uri.pathSegments.first, pathSegments: uri.pathSegments.skip(1).toList()); } - if (uri.scheme.isEmpty) { - uri = - uri.replace(scheme: (webSockets ? 'ws' : 'http') + (secure ? 's' : '')); - } + uri = uri.replace(scheme: (webSockets ? 'ws' : 'http') + (secure ? 's' : '')); if (!uri.hasPort) { uri = uri.replace(port: kDefaultPort); } diff --git a/app/lib/bloc/settings.dart b/app/lib/bloc/settings.dart index 5b3c3d57..a19e35a3 100644 --- a/app/lib/bloc/settings.dart +++ b/app/lib/bloc/settings.dart @@ -31,6 +31,8 @@ enum ThemeDensity { }; } +const kDefaultServerList = ['https://servers.setonix.linwood.dev/data.json']; + final class ThemeModeMapper extends SimpleMapper { const ThemeModeMapper(); @@ -63,6 +65,8 @@ class SetonixSettings with SetonixSettingsMappable implements LeapSettings { final List swamps; final double scrollSensitivity; final ThemeDensity density; + final List serverList; + final bool showIntro; const SetonixSettings({ this.localeTag = '', @@ -81,6 +85,8 @@ class SetonixSettings with SetonixSettingsMappable implements LeapSettings { this.swamps = const [], this.density = ThemeDensity.system, this.scrollSensitivity = 1, + this.serverList = const [], + this.showIntro = true, }); Locale? get locale { @@ -118,6 +124,8 @@ class SetonixSettings with SetonixSettingsMappable implements LeapSettings { density: ThemeDensity.values.byName( prefs.getString('density') ?? ThemeDensity.system.name, ), + serverList: prefs.getStringList('serverList') ?? [], + showIntro: prefs.getBool('showIntro') ?? true, ); Future save() async { @@ -145,6 +153,8 @@ class SetonixSettings with SetonixSettingsMappable implements LeapSettings { await prefs.setStringList('swamps', swamps); await prefs.setDouble('scrollSensitivity', scrollSensitivity); await prefs.setString('density', density.name); + await prefs.setStringList('serverList', serverList); + await prefs.setBool('showIntro', showIntro); } } @@ -269,6 +279,20 @@ class SettingsCubit extends Cubit return save(); } + Future addServersToList(List server) { + final newList = {...state.serverList, ...server}.toList(); + emit(state.copyWith(serverList: newList)); + return save(); + } + + Future addServerToList(String server) => addServersToList([server]); + + Future removeServerFromList(String server) { + final newList = state.serverList.where((s) => s != server).toList(); + emit(state.copyWith(serverList: newList)); + return save(); + } + Future importSettings(String data) { final settings = SetonixSettingsMapper.fromJson(data); emit(settings); diff --git a/app/lib/bloc/settings.mapper.dart b/app/lib/bloc/settings.mapper.dart index ace703d4..19eae421 100644 --- a/app/lib/bloc/settings.mapper.dart +++ b/app/lib/bloc/settings.mapper.dart @@ -135,6 +135,12 @@ class SetonixSettingsMapper extends ClassMapperBase { static double _$scrollSensitivity(SetonixSettings v) => v.scrollSensitivity; static const Field _f$scrollSensitivity = Field('scrollSensitivity', _$scrollSensitivity, opt: true, def: 1); + static List _$serverList(SetonixSettings v) => v.serverList; + static const Field> _f$serverList = + Field('serverList', _$serverList, opt: true, def: const []); + static bool _$showIntro(SetonixSettings v) => v.showIntro; + static const Field _f$showIntro = + Field('showIntro', _$showIntro, opt: true, def: true); @override final MappableFields fields = const { @@ -154,6 +160,8 @@ class SetonixSettingsMapper extends ClassMapperBase { #swamps: _f$swamps, #density: _f$density, #scrollSensitivity: _f$scrollSensitivity, + #serverList: _f$serverList, + #showIntro: _f$showIntro, }; static SetonixSettings _instantiate(DecodingData data) { @@ -173,7 +181,9 @@ class SetonixSettingsMapper extends ClassMapperBase { zoom: data.dec(_f$zoom), swamps: data.dec(_f$swamps), density: data.dec(_f$density), - scrollSensitivity: data.dec(_f$scrollSensitivity)); + scrollSensitivity: data.dec(_f$scrollSensitivity), + serverList: data.dec(_f$serverList), + showIntro: data.dec(_f$showIntro)); } @override @@ -234,6 +244,7 @@ abstract class SetonixSettingsCopyWith<$R, $In extends SetonixSettings, $Out> ListCopyWith<$R, ListGameServer, ListGameServerCopyWith<$R, ListGameServer, ListGameServer>> get servers; ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get swamps; + ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get serverList; $R call( {String? localeTag, ThemeMode? theme, @@ -250,7 +261,9 @@ abstract class SetonixSettingsCopyWith<$R, $In extends SetonixSettings, $Out> double? zoom, List? swamps, ThemeDensity? density, - double? scrollSensitivity}); + double? scrollSensitivity, + List? serverList, + bool? showIntro}); SetonixSettingsCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( Then<$Out2, $R2> t); } @@ -276,6 +289,10 @@ class _SetonixSettingsCopyWithImpl<$R, $Out> ListCopyWith($value.swamps, (v, t) => ObjectCopyWith(v, $identity, t), (v) => call(swamps: v)); @override + ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get serverList => + ListCopyWith($value.serverList, (v, t) => ObjectCopyWith(v, $identity, t), + (v) => call(serverList: v)); + @override $R call( {String? localeTag, ThemeMode? theme, @@ -292,7 +309,9 @@ class _SetonixSettingsCopyWithImpl<$R, $Out> double? zoom, List? swamps, ThemeDensity? density, - double? scrollSensitivity}) => + double? scrollSensitivity, + List? serverList, + bool? showIntro}) => $apply(FieldCopyWithData({ if (localeTag != null) #localeTag: localeTag, if (theme != null) #theme: theme, @@ -309,7 +328,9 @@ class _SetonixSettingsCopyWithImpl<$R, $Out> if (zoom != null) #zoom: zoom, if (swamps != null) #swamps: swamps, if (density != null) #density: density, - if (scrollSensitivity != null) #scrollSensitivity: scrollSensitivity + if (scrollSensitivity != null) #scrollSensitivity: scrollSensitivity, + if (serverList != null) #serverList: serverList, + if (showIntro != null) #showIntro: showIntro })); @override SetonixSettings $make(CopyWithData data) => SetonixSettings( @@ -330,7 +351,9 @@ class _SetonixSettingsCopyWithImpl<$R, $Out> swamps: data.get(#swamps, or: $value.swamps), density: data.get(#density, or: $value.density), scrollSensitivity: - data.get(#scrollSensitivity, or: $value.scrollSensitivity)); + data.get(#scrollSensitivity, or: $value.scrollSensitivity), + serverList: data.get(#serverList, or: $value.serverList), + showIntro: data.get(#showIntro, or: $value.showIntro)); @override SetonixSettingsCopyWith<$R2, SetonixSettings, $Out2> $chain<$R2, $Out2>( diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index 64daf48c..53637257 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -262,5 +262,7 @@ "backupAllKeys": "Backup all keys", "noAccount": "There are no accounts available", "authenticateRequired": "Authentication is required to connect to the server", - "authenticateLoading": "Authenticating..." + "authenticateLoading": "Authenticating...", + "loadDefaultServerList": "Load default server list", + "serverLists": "Server lists" } diff --git a/app/lib/main.dart b/app/lib/main.dart index 941d18df..870edb5d 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -4,10 +4,12 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; +import 'package:setonix/pages/settings/accounts.dart'; import 'package:setonix/pages/settings/data.dart'; import 'package:setonix/pages/settings/general.dart'; import 'package:setonix/pages/settings/input.dart'; import 'package:setonix/pages/settings/personalization.dart'; +import 'package:setonix/pages/settings/servers.dart'; import 'package:setonix/src/generated/i18n/app_localizations.dart'; import 'package:flutter_web_plugins/url_strategy.dart'; import 'package:material_leap/material_leap.dart'; @@ -176,6 +178,14 @@ class SetonixApp extends StatelessWidget { path: 'inputs', builder: (context, state) => const InputsSettingsPage(), ), + GoRoute( + path: 'accounts', + builder: (context, state) => const AccountsSettingsPage(), + ), + GoRoute( + path: 'serverlist', + builder: (context, state) => const ServersSettingsPage(), + ), ], ), ], diff --git a/app/lib/pages/settings/home.dart b/app/lib/pages/settings/home.dart index 6e7f9c47..38c51087 100644 --- a/app/lib/pages/settings/home.dart +++ b/app/lib/pages/settings/home.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:setonix/pages/settings/accounts.dart'; import 'package:setonix/pages/settings/input.dart'; +import 'package:setonix/pages/settings/servers.dart'; import 'package:setonix/src/generated/i18n/app_localizations.dart'; import 'package:go_router/go_router.dart'; import 'package:material_leap/material_leap.dart'; @@ -15,7 +16,8 @@ enum SettingsView { data, personalization, accounts, - inputs; + inputs, + servers; bool get isEnabled => true; @@ -26,6 +28,7 @@ enum SettingsView { AppLocalizations.of(context).personalization, SettingsView.accounts => AppLocalizations.of(context).accounts, SettingsView.inputs => AppLocalizations.of(context).inputs, + SettingsView.servers => AppLocalizations.of(context).serverLists, }; IconGetter get icon => switch (this) { @@ -34,6 +37,7 @@ enum SettingsView { SettingsView.personalization => PhosphorIcons.monitor, SettingsView.accounts => PhosphorIcons.user, SettingsView.inputs => PhosphorIcons.keyboard, + SettingsView.servers => PhosphorIcons.list, }; String get path => '/settings/$name'; @@ -44,6 +48,7 @@ enum SettingsView { PersonalizationSettingsPage(inView: inView), SettingsView.accounts => AccountsSettingsPage(inView: inView), SettingsView.inputs => InputsSettingsPage(inView: inView), + SettingsView.servers => ServersSettingsPage(inView: inView), }; } diff --git a/app/lib/pages/settings/servers.dart b/app/lib/pages/settings/servers.dart new file mode 100644 index 00000000..4c3a2dfb --- /dev/null +++ b/app/lib/pages/settings/servers.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:setonix/src/generated/i18n/app_localizations.dart'; +import 'package:material_leap/material_leap.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; + +import '../../bloc/settings.dart'; + +class ServersSettingsPage extends StatelessWidget { + final bool inView; + const ServersSettingsPage({super.key, this.inView = false}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: inView ? Colors.transparent : null, + appBar: WindowTitleBar( + inView: inView, + backgroundColor: inView ? Colors.transparent : null, + title: Text(AppLocalizations.of(context).servers), + actions: [ + IconButton( + icon: const PhosphorIcon(PhosphorIconsLight.clockCounterClockwise), + tooltip: AppLocalizations.of(context).loadDefaultServerList, + onPressed: () async { + context + .read() + .addServersToList(kDefaultServerList); + }, + ), + ], + ), + body: BlocBuilder( + buildWhen: (previous, current) => + previous.serverList != current.serverList, + builder: (context, state) { + final servers = state.serverList; + if (servers.isEmpty) { + return Center( + child: Text( + AppLocalizations.of(context).noServers, + style: Theme.of(context).textTheme.bodyLarge, + ), + ); + } + return ListView.builder( + itemCount: servers.length, + itemBuilder: (context, index) { + final server = servers[index]; + + void deleteServer() => + context.read().removeServerFromList(server); + + return Dismissible( + key: Key(server), + child: ContextRegion( + builder: (context, button, controller) => ListTile( + title: Text(server), + trailing: button, + ), + menuChildren: [ + MenuItemButton( + leadingIcon: const PhosphorIcon(PhosphorIconsLight.trash), + onPressed: deleteServer, + child: Text(AppLocalizations.of(context).delete), + ), + ], + ), + onDismissed: (direction) => deleteServer(), + ); + }, + ); + }, + ), + floatingActionButton: FloatingActionButton.extended( + onPressed: () async { + final settingsCubit = context.read(); + final name = await showDialog( + context: context, builder: (context) => NameDialog()); + if (name == null) return; + var uri = Uri.tryParse(name); + if (uri == null) return; + if (!uri.hasScheme) { + uri = uri.replace(scheme: 'https'); + } + settingsCubit.addServerToList(name); + }, + label: Text(AppLocalizations.of(context).add), + icon: const PhosphorIcon(PhosphorIconsLight.plus), + ), + ); + } +} From f9cc125c107d12086d06cdba84856c0740a917b6 Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Mon, 16 Jun 2025 14:18:22 +0200 Subject: [PATCH 13/18] Add intro --- app/lib/bloc/settings.dart | 17 +++++-- app/lib/l10n/app_en.arb | 6 ++- app/lib/pages/home/page.dart | 29 +++++++++++- app/lib/pages/settings/general.dart | 9 ++++ app/lib/pages/settings/intro.dart | 73 +++++++++++++++++++++++++++++ app/lib/pages/settings/servers.dart | 34 ++++++++++++-- 6 files changed, 159 insertions(+), 9 deletions(-) create mode 100644 app/lib/pages/settings/intro.dart diff --git a/app/lib/bloc/settings.dart b/app/lib/bloc/settings.dart index a19e35a3..fd55416d 100644 --- a/app/lib/bloc/settings.dart +++ b/app/lib/bloc/settings.dart @@ -31,7 +31,13 @@ enum ThemeDensity { }; } -const kDefaultServerList = ['https://servers.setonix.linwood.dev/data.json']; +List getDefaultServerList() { + final env = String.fromEnvironment('server_list', defaultValue: ''); + if (env.isNotEmpty) { + return env.split(',').map((e) => e.trim()).toList(); + } + return ['https://servers.setonix.linwood.dev/data.json']; +} final class ThemeModeMapper extends SimpleMapper { const ThemeModeMapper(); @@ -279,8 +285,8 @@ class SettingsCubit extends Cubit return save(); } - Future addServersToList(List server) { - final newList = {...state.serverList, ...server}.toList(); + Future addServersToList(List server, [bool reset = false]) { + final newList = {if (!reset) ...state.serverList, ...server}.toList(); emit(state.copyWith(serverList: newList)); return save(); } @@ -293,6 +299,11 @@ class SettingsCubit extends Cubit return save(); } + Future changeShowIntro(bool value) { + emit(state.copyWith(showIntro: value)); + return save(); + } + Future importSettings(String data) { final settings = SetonixSettingsMapper.fromJson(data); emit(settings); diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index 53637257..7277fbfd 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -264,5 +264,9 @@ "authenticateRequired": "Authentication is required to connect to the server", "authenticateLoading": "Authenticating...", "loadDefaultServerList": "Load default server list", - "serverLists": "Server lists" + "loadDefaultServerListDescription": "This will load the default server list from the Setonix website when opening the servers dialog.", + "serverLists": "Server lists", + "reset": "Reset", + "confirm": "Confirm", + "showIntro": "Show intro" } diff --git a/app/lib/pages/home/page.dart b/app/lib/pages/home/page.dart index 542ab94b..c49b18df 100644 --- a/app/lib/pages/home/page.dart +++ b/app/lib/pages/home/page.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:setonix/pages/settings/intro.dart'; import 'package:setonix/src/generated/i18n/app_localizations.dart'; import 'package:material_leap/material_leap.dart'; import 'package:phosphor_flutter/phosphor_flutter.dart'; @@ -12,10 +14,33 @@ import 'package:setonix/pages/home/play.dart'; import '../../api/settings.dart'; -class HomePage extends StatelessWidget { +class HomePage extends StatefulWidget { + const HomePage({super.key}); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { final ScrollController _scrollController = ScrollController(); - HomePage({super.key}); + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) => _showIntro()); + } + + void _showIntro() { + if (context.read().state.showIntro) { + showDialog(context: context, builder: (context) => IntroDialog()); + } + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } List<(String, IconData, VoidCallback, Widget?, bool)> _getItems( BuildContext context) => diff --git a/app/lib/pages/settings/general.dart b/app/lib/pages/settings/general.dart index 0a90755e..e33d0c5a 100644 --- a/app/lib/pages/settings/general.dart +++ b/app/lib/pages/settings/general.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:material_leap/material_leap.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:phosphor_flutter/phosphor_flutter.dart'; +import 'package:setonix/pages/settings/intro.dart'; import 'package:setonix/src/generated/i18n/app_localizations.dart'; import 'package:setonix/bloc/settings.dart'; import 'package:setonix/theme.dart'; @@ -184,6 +185,14 @@ class _GeneralSettingsPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + ListTile( + leading: const PhosphorIcon(PhosphorIconsLight.info), + title: Text(AppLocalizations.of(context).showIntro), + onTap: () => showDialog( + context: context, + builder: (context) => IntroDialog(), + ), + ), ListTile( leading: const PhosphorIcon(PhosphorIconsLight.article), diff --git a/app/lib/pages/settings/intro.dart b/app/lib/pages/settings/intro.dart new file mode 100644 index 00000000..da05f444 --- /dev/null +++ b/app/lib/pages/settings/intro.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:material_leap/material_leap.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; +import 'package:setonix/bloc/settings.dart'; +import 'package:setonix/src/generated/i18n/app_localizations.dart'; + +class IntroDialog extends StatefulWidget { + const IntroDialog({super.key}); + + @override + State createState() => _IntroDialogState(); +} + +class _IntroDialogState extends State { + bool _loadDefaultServers = true; + late final SettingsCubit _settingsCubit; + + @override + void initState() { + super.initState(); + _settingsCubit = context.read(); + } + + @override + void dispose() async { + super.dispose(); + await _settingsCubit.changeShowIntro(false); + if (_loadDefaultServers) { + await _settingsCubit.addServersToList(getDefaultServerList(), true); + } + } + + @override + Widget build(BuildContext context) { + return ResponsiveAlertDialog( + title: Text(AppLocalizations.of(context).welcome), + constraints: const BoxConstraints(maxWidth: LeapBreakpoints.compact), + content: ListView( + shrinkWrap: true, + children: [ + Image.asset( + 'images/logo.png', + width: 100, + height: 100, + ), + Text( + AppLocalizations.of(context).welcomeContent, + style: Theme.of(context).textTheme.bodyLarge, + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + const Divider(), + SwitchListTile( + value: _loadDefaultServers, + onChanged: (e) => setState(() => _loadDefaultServers = e), + title: Text(AppLocalizations.of(context).loadDefaultServerList), + subtitle: Text( + AppLocalizations.of(context).loadDefaultServerListDescription), + ), + const SizedBox(height: 16), + FilledButton.icon( + onPressed: () { + Navigator.of(context).pop(); + }, + icon: const PhosphorIcon(PhosphorIconsLight.check), + label: Text(AppLocalizations.of(context).confirm), + ), + ], + ), + ); + } +} diff --git a/app/lib/pages/settings/servers.dart b/app/lib/pages/settings/servers.dart index 4c3a2dfb..4bd6795d 100644 --- a/app/lib/pages/settings/servers.dart +++ b/app/lib/pages/settings/servers.dart @@ -23,9 +23,37 @@ class ServersSettingsPage extends StatelessWidget { icon: const PhosphorIcon(PhosphorIconsLight.clockCounterClockwise), tooltip: AppLocalizations.of(context).loadDefaultServerList, onPressed: () async { - context - .read() - .addServersToList(kDefaultServerList); + final settingsCubit = context.read(); + showDialog( + context: context, + builder: (context) => AlertDialog( + title: + Text(AppLocalizations.of(context).loadDefaultServerList), + content: Text(AppLocalizations.of(context) + .loadDefaultServerListDescription), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text(AppLocalizations.of(context).cancel), + ), + OutlinedButton( + onPressed: () { + settingsCubit.addServersToList( + getDefaultServerList(), true); + Navigator.of(context).pop(true); + }, + child: Text(AppLocalizations.of(context).reset), + ), + ElevatedButton( + onPressed: () { + settingsCubit.addServersToList(getDefaultServerList()); + Navigator.of(context).pop(true); + }, + child: Text(AppLocalizations.of(context).confirm), + ), + ], + ), + ); }, ), ], From 036bf45a550889f18abb57eb4e58e27b6a796543 Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Mon, 16 Jun 2025 15:02:19 +0200 Subject: [PATCH 14/18] Implement server browsing in ui --- api/lib/src/models/server.dart | 11 +++ api/lib/src/models/server.mapper.dart | 136 ++++++++++++++++++++++++++ app/lib/l10n/app_en.arb | 3 +- app/lib/pages/home/connect.dart | 106 ++++++++++++++------ app/lib/services/network.dart | 123 +++++++++++++++++------ servers/src/pages/data.json.ts | 2 +- 6 files changed, 322 insertions(+), 59 deletions(-) diff --git a/api/lib/src/models/server.dart b/api/lib/src/models/server.dart index 4eb52384..6d2bab03 100644 --- a/api/lib/src/models/server.dart +++ b/api/lib/src/models/server.dart @@ -46,6 +46,17 @@ final class ListGameServer extends GameServer with ListGameServerMappable { buildServerAddress(Uri.parse(address), secure, webSockets: webSockets); } +@MappableClass() +final class BrowsedGameServer extends ListGameServer + with BrowsedGameServerMappable { + BrowsedGameServer({ + super.name = '', + required super.address, + super.secure = true, + super.highlighted = false, + }); +} + Uri buildServerAddress(Uri uri, bool secure, {bool webSockets = true}) { // handle plain host without scheme: treat single-segment path as host if (uri.host.isEmpty && uri.pathSegments.length == 1) { diff --git a/api/lib/src/models/server.mapper.dart b/api/lib/src/models/server.mapper.dart index 36993da8..b94a99a0 100644 --- a/api/lib/src/models/server.mapper.dart +++ b/api/lib/src/models/server.mapper.dart @@ -186,6 +186,7 @@ class ListGameServerMapper extends ClassMapperBase { if (_instance == null) { MapperContainer.globals.use(_instance = ListGameServerMapper._()); GameServerMapper.ensureInitialized(); + BrowsedGameServerMapper.ensureInitialized(); } return _instance!; } @@ -311,6 +312,141 @@ class _ListGameServerCopyWithImpl<$R, $Out> _ListGameServerCopyWithImpl<$R2, $Out2>($value, $cast, t); } +class BrowsedGameServerMapper extends ClassMapperBase { + BrowsedGameServerMapper._(); + + static BrowsedGameServerMapper? _instance; + static BrowsedGameServerMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = BrowsedGameServerMapper._()); + ListGameServerMapper.ensureInitialized(); + } + return _instance!; + } + + @override + final String id = 'BrowsedGameServer'; + + static String _$name(BrowsedGameServer v) => v.name; + static const Field _f$name = + Field('name', _$name, opt: true, def: ''); + static String _$address(BrowsedGameServer v) => v.address; + static const Field _f$address = + Field('address', _$address); + static bool _$secure(BrowsedGameServer v) => v.secure; + static const Field _f$secure = + Field('secure', _$secure, opt: true, def: true); + static bool _$highlighted(BrowsedGameServer v) => v.highlighted; + static const Field _f$highlighted = + Field('highlighted', _$highlighted, opt: true, def: false); + + @override + final MappableFields fields = const { + #name: _f$name, + #address: _f$address, + #secure: _f$secure, + #highlighted: _f$highlighted, + }; + + static BrowsedGameServer _instantiate(DecodingData data) { + return BrowsedGameServer( + name: data.dec(_f$name), + address: data.dec(_f$address), + secure: data.dec(_f$secure), + highlighted: data.dec(_f$highlighted)); + } + + @override + final Function instantiate = _instantiate; + + static BrowsedGameServer fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static BrowsedGameServer fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin BrowsedGameServerMappable { + String toJson() { + return BrowsedGameServerMapper.ensureInitialized() + .encodeJson(this as BrowsedGameServer); + } + + Map toMap() { + return BrowsedGameServerMapper.ensureInitialized() + .encodeMap(this as BrowsedGameServer); + } + + BrowsedGameServerCopyWith + get copyWith => + _BrowsedGameServerCopyWithImpl( + this as BrowsedGameServer, $identity, $identity); + @override + String toString() { + return BrowsedGameServerMapper.ensureInitialized() + .stringifyValue(this as BrowsedGameServer); + } + + @override + bool operator ==(Object other) { + return BrowsedGameServerMapper.ensureInitialized() + .equalsValue(this as BrowsedGameServer, other); + } + + @override + int get hashCode { + return BrowsedGameServerMapper.ensureInitialized() + .hashValue(this as BrowsedGameServer); + } +} + +extension BrowsedGameServerValueCopy<$R, $Out> + on ObjectCopyWith<$R, BrowsedGameServer, $Out> { + BrowsedGameServerCopyWith<$R, BrowsedGameServer, $Out> + get $asBrowsedGameServer => $base + .as((v, t, t2) => _BrowsedGameServerCopyWithImpl<$R, $Out>(v, t, t2)); +} + +abstract class BrowsedGameServerCopyWith<$R, $In extends BrowsedGameServer, + $Out> implements ListGameServerCopyWith<$R, $In, $Out> { + @override + $R call({String? name, String? address, bool? secure, bool? highlighted}); + BrowsedGameServerCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t); +} + +class _BrowsedGameServerCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, BrowsedGameServer, $Out> + implements BrowsedGameServerCopyWith<$R, BrowsedGameServer, $Out> { + _BrowsedGameServerCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + BrowsedGameServerMapper.ensureInitialized(); + @override + $R call({String? name, String? address, bool? secure, bool? highlighted}) => + $apply(FieldCopyWithData({ + if (name != null) #name: name, + if (address != null) #address: address, + if (secure != null) #secure: secure, + if (highlighted != null) #highlighted: highlighted + })); + @override + BrowsedGameServer $make(CopyWithData data) => BrowsedGameServer( + name: data.get(#name, or: $value.name), + address: data.get(#address, or: $value.address), + secure: data.get(#secure, or: $value.secure), + highlighted: data.get(#highlighted, or: $value.highlighted)); + + @override + BrowsedGameServerCopyWith<$R2, BrowsedGameServer, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _BrowsedGameServerCopyWithImpl<$R2, $Out2>($value, $cast, t); +} + class GamePropertyMapper extends ClassMapperBase { GamePropertyMapper._(); diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index 7277fbfd..f6eeffdc 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -268,5 +268,6 @@ "serverLists": "Server lists", "reset": "Reset", "confirm": "Confirm", - "showIntro": "Show intro" + "showIntro": "Show intro", + "highlighted": "Highlighted" } diff --git a/app/lib/pages/home/connect.dart b/app/lib/pages/home/connect.dart index baf0f1a8..a2576d51 100644 --- a/app/lib/pages/home/connect.dart +++ b/app/lib/pages/home/connect.dart @@ -34,6 +34,7 @@ class ConnectEditDialog extends StatelessWidget { String address = initialValue?.address ?? ''; String name = initialValue?.name ?? ''; bool secure = initialValue?.secure ?? true; + bool highlighted = initialValue?.highlighted ?? true; final secureSwitchEnabled = !kIsWeb || Uri.base.isScheme('HTTP'); @@ -74,6 +75,13 @@ class ConnectEditDialog extends StatelessWidget { onFieldSubmitted: index == null ? (_) => connect() : null, ), const SizedBox(height: 8), + StatefulBuilder( + builder: (context, setState) => SwitchListTile( + title: Text(AppLocalizations.of(context).highlighted), + value: highlighted, + onChanged: (value) => setState(() => highlighted = value), + ), + ), if (secureSwitchEnabled) StatefulBuilder( builder: (context, setState) => SwitchListTile( @@ -93,6 +101,7 @@ class ConnectEditDialog extends StatelessWidget { address: address, secure: secure, name: name, + highlighted: highlighted, ); if (index != null) { cubit.updateServer(index!, updated); @@ -123,7 +132,7 @@ class ServersDialog extends StatefulWidget { } class _ServersDialogState extends State { - late final Stream> _servers; + Stream>? _servers; bool _isMobileOpen = false; (GameServer, int)? _selected; @@ -132,11 +141,25 @@ class _ServersDialogState extends State { @override void initState() { super.initState(); - _servers = ValueConnectableStream( - context.read().fetchServersWithProperties()) + _buildServersStream(); + } + + void _buildServersStream([SetonixSettings? settings]) { + settings ??= context.read().state; + _servers = ValueConnectableStream(context + .read() + .fetchServersWithProperties( + browsable: settings.showConnectNetwork, + local: settings.showConnectYour)) .autoConnect(); } + void _refreshServers(SetonixSettings settings) { + setState(() { + _buildServersStream(settings); + }); + } + Text _buildDetails(BuildContext context, GameProperty property) => Text('${property.currentPlayers}/${property.maxPlayers ?? '?'}'); @@ -148,12 +171,17 @@ class _ServersDialogState extends State { ]; @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocConsumer( + listenWhen: (previous, current) => + previous.serverList != current.serverList || + previous.showConnectYour != current.showConnectYour || + previous.showConnectNetwork != current.showConnectNetwork, buildWhen: (previous, current) => previous.showConnectYour != current.showConnectYour || previous.showConnectNetwork != current.showConnectNetwork, + listener: (context, state) => _refreshServers(state), builder: (context, settings) => ResponsiveAlertDialog( - title: Text(AppLocalizations.of(context).connect), + title: Text(AppLocalizations.of(context).servers), leading: IconButton.outlined( icon: const Icon(PhosphorIconsLight.x), onPressed: () => Navigator.of(context).pop(), @@ -275,12 +303,34 @@ class _ServersDialogState extends State { itemBuilder: (context, index) { final entry = servers[index]; final current = entry.key; + final primaryColor = + ColorScheme.of(context).primary; + final defaultColor = IconTheme.of(context).color; return ListTile( title: Text(current.display), - leading: switch (current) { + trailing: switch (current) { LanGameServer() => - const Icon(PhosphorIconsLight.globe), - _ => null, + const Icon(PhosphorIconsLight.mapPin), + BrowsedGameServer() => Icon( + PhosphorIcons.globe( + current.highlighted + ? PhosphorIconsStyle.fill + : PhosphorIconsStyle.light, + ), + color: current.highlighted + ? primaryColor + : defaultColor, + ), + ListGameServer() => Icon( + PhosphorIcons.puzzlePiece( + current.highlighted + ? PhosphorIconsStyle.fill + : PhosphorIconsStyle.light, + ), + color: current.highlighted + ? primaryColor + : defaultColor, + ), }, onTap: () { setState(() { @@ -347,26 +397,26 @@ class _ServersDialogState extends State { onSearchChanged: (value) => setState(() { _search = value; }), - children: const [ - /* InputChip( - label: Text(AppLocalizations.of(context).yourServers), - avatar: const Icon(PhosphorIconsLight.puzzlePiece), - showCheckmark: false, - selected: settings.showConnectYour, - onPressed: () => context - .read() - .changeShowConnectYour(!settings.showConnectYour), - ), - InputChip( - label: Text(AppLocalizations.of(context).inNetwork), - avatar: const Icon(PhosphorIconsLight.globe), - showCheckmark: false, - selected: settings.showConnectNetwork, - onPressed: () => context - .read() - .changeShowConnectNetwork( - !settings.showConnectNetwork), - ), */ + children: [ + InputChip( + label: Text(AppLocalizations.of(context).yourServers), + avatar: const Icon(PhosphorIconsLight.puzzlePiece), + showCheckmark: false, + selected: settings.showConnectYour, + onPressed: () => context + .read() + .changeShowConnectYour(!settings.showConnectYour), + ), + InputChip( + label: Text(AppLocalizations.of(context).browse), + avatar: const Icon(PhosphorIconsLight.globe), + showCheckmark: false, + selected: settings.showConnectNetwork, + onPressed: () => context + .read() + .changeShowConnectNetwork( + !settings.showConnectNetwork), + ), ]), const SizedBox(height: 8), Expanded( diff --git a/app/lib/services/network.dart b/app/lib/services/network.dart index 23badedd..f18e3287 100644 --- a/app/lib/services/network.dart +++ b/app/lib/services/network.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:flutter/foundation.dart'; @@ -129,45 +130,109 @@ class NetworkService { } } + Future _fetchServer( + GameServer server, + LanProperty? property, { + Map> cached = const {}, + }) async { + property ??= const LanProperty(); + return cached[server] ?? + switch (server) { + LanGameServer() => + Future.value(GameProperty(description: property.description)), + ListGameServer() => cached[server] = + fetchInfo(server.buildAddress(webSockets: false)) + .onError((_, __) => null), + }; + } + + Stream> _fetchBrowsableServersWithProperties({ + Map>? cached, + required Map returned, + }) { + final lists = settingsCubit.state.serverList; + Future> fetchList(String address) async { + try { + final response = await http.get(Uri.parse(address), + headers: {HttpHeaders.contentTypeHeader: 'application/json'}); + if (response.statusCode != HttpStatus.ok) return []; + final result = jsonDecode(response.body) as Map; + return result['servers'] + .map((e) => BrowsedGameServerMapper.fromMap(e)) + .toList(); + } catch (_) { + return []; + } + } + + final currentCached = cached ?? >{}; + Stream> fetchServers(String address) async* { + final servers = await fetchList(address); + for (final server in servers) { + returned.putIfAbsent( + server, + () => const GameProperty(), + ); + } + yield returned; + for (final server in servers) { + try { + returned[server] = + await _fetchServer(server, null, cached: currentCached); + } catch (_) { + returned[server] = null; + } + yield returned; + } + } + + final streams = lists.map((e) => fetchServers(e)); + return Rx.merge(streams); + } + Stream> fetchServersWithProperties({ - bool list = true, + bool browsable = true, + bool local = true, }) async* { Map> cached = {}; - Future fetch( - GameServer server, LanProperty? property) async { - property ??= const LanProperty(); - return cached[server] ?? - switch (server) { - LanGameServer() => - Future.value(GameProperty(description: property.description)), - ListGameServer() => cached[server] = - fetchInfo(server.buildAddress(webSockets: false)) - .onError((_, __) => null), - }; - } - final returned = {}; yield returned; + fetchLocal() async* { + await for (final event in _servers) { + returned.removeWhere((key, value) => !event.any((e) => e.$1 == key)); + for (final (server, _) in event) { + returned[server] = const GameProperty(); + } + yield returned; - await for (final event in _servers) { - returned.removeWhere((key, value) => !event.any((e) => e.$1 == key)); - for (final (server, _) in event) { - returned[server] = const GameProperty(); - } - yield returned; + if (event.isEmpty) { + continue; + } - if (event.isEmpty) { - continue; + for (final e in event) { + try { + returned[e.$1] = await _fetchServer(e.$1, e.$2, cached: cached); + } catch (_) { + returned[e.$1] = null; + } + yield returned; + } } + } - for (final e in event) { - try { - returned[e.$1] = await fetch(e.$1, e.$2); - } catch (_) { - returned[e.$1] = null; - } - yield returned; + if (browsable) { + if (!local) { + yield* _fetchBrowsableServersWithProperties( + cached: cached, returned: returned); + } else { + yield* Rx.merge([ + fetchLocal(), + _fetchBrowsableServersWithProperties( + cached: cached, returned: returned), + ]); } + } else if (local) { + yield* fetchLocal(); } } } diff --git a/servers/src/pages/data.json.ts b/servers/src/pages/data.json.ts index c5a57e7e..b6d3212e 100644 --- a/servers/src/pages/data.json.ts +++ b/servers/src/pages/data.json.ts @@ -3,7 +3,7 @@ import { loadServersFromConfig } from '../scripts/servers/list'; export async function GET() { const servers = await loadServersFromConfig(); - return new Response(JSON.stringify(servers), { + return new Response(JSON.stringify({servers}), { status: 200, headers: { 'Content-Type': 'application/json' }, }); From 4d3f02940838588fc9b8db34fbb0c56d4124133f Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Mon, 16 Jun 2025 15:12:50 +0200 Subject: [PATCH 15/18] Indicate secure, add bold for highlight --- app/lib/pages/home/connect.dart | 70 ++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/app/lib/pages/home/connect.dart b/app/lib/pages/home/connect.dart index a2576d51..f4619fe5 100644 --- a/app/lib/pages/home/connect.dart +++ b/app/lib/pages/home/connect.dart @@ -160,8 +160,37 @@ class _ServersDialogState extends State { }); } - Text _buildDetails(BuildContext context, GameProperty property) => - Text('${property.currentPlayers}/${property.maxPlayers ?? '?'}'); + Widget _buildTitle(BuildContext context, GameServer server) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + server.display, + style: Theme.of(context).textTheme.titleLarge, + ), + if (server is ListGameServer && server.name.isNotEmpty) + Text( + server.address, + style: Theme.of(context).textTheme.titleMedium, + ), + ], + ); + } + + Widget _buildDetails( + BuildContext context, bool secure, GameProperty property) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(secure + ? PhosphorIconsLight.shieldCheck + : PhosphorIconsLight.shieldSlash), + const SizedBox(width: 8), + Text('${property.currentPlayers}/${property.maxPlayers ?? '?'}'), + ], + ); + } List _buildDetailsChildren(GameProperty server) => [ ListTile( @@ -306,28 +335,35 @@ class _ServersDialogState extends State { final primaryColor = ColorScheme.of(context).primary; final defaultColor = IconTheme.of(context).color; + final highlighted = current is ListGameServer && + current.highlighted; return ListTile( - title: Text(current.display), + title: Text(current.display, + style: TextStyle( + fontWeight: highlighted + ? FontWeight.w800 + : FontWeight.normal, + )), trailing: switch (current) { LanGameServer() => const Icon(PhosphorIconsLight.mapPin), BrowsedGameServer() => Icon( PhosphorIcons.globe( - current.highlighted + highlighted ? PhosphorIconsStyle.fill : PhosphorIconsStyle.light, ), - color: current.highlighted + color: highlighted ? primaryColor : defaultColor, ), ListGameServer() => Icon( PhosphorIcons.puzzlePiece( - current.highlighted + highlighted ? PhosphorIconsStyle.fill : PhosphorIconsStyle.light, ), - color: current.highlighted + color: highlighted ? primaryColor : defaultColor, ), @@ -341,7 +377,7 @@ class _ServersDialogState extends State { showLeapBottomSheet( context: context, titleBuilder: (context) => - Text(server.display), + _buildTitle(context, current), actionsBuilder: (context) => [ if (property != null) ...[ DefaultTextStyle( @@ -349,8 +385,8 @@ class _ServersDialogState extends State { .textTheme .headlineSmall ?? const TextStyle(fontSize: 20), - child: _buildDetails( - context, property), + child: _buildDetails(context, + current.secure, property), ), const SizedBox(width: 8), ], @@ -443,18 +479,16 @@ class _ServersDialogState extends State { .titleLarge ?? const TextStyle(), child: Row( + crossAxisAlignment: + CrossAxisAlignment.start, children: [ Expanded( - child: Text( - server.display, - style: Theme.of(context) - .textTheme - .titleLarge, - ), + child: _buildTitle( + context, server), ), const SizedBox(width: 8), - _buildDetails( - context, property), + _buildDetails(context, + server.secure, property), ], ), ), From 3bf41fc41fbbcd747c635078e8d8f5b66874eef8 Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Mon, 16 Jun 2025 15:30:43 +0200 Subject: [PATCH 16/18] Add use remote status to improve performance --- app/lib/bloc/settings.dart | 12 ++++----- app/lib/bloc/settings.mapper.dart | 20 +++++++-------- app/lib/pages/home/connect.dart | 12 ++++----- servers/astro.config.mjs | 5 ++++ servers/src/scripts/servers/info.ts | 26 ++++++++++++++----- servers/src/scripts/servers/list.ts | 38 +++++++++++++++++----------- servers/src/scripts/servers/utils.ts | 1 + 7 files changed, 70 insertions(+), 44 deletions(-) diff --git a/app/lib/bloc/settings.dart b/app/lib/bloc/settings.dart index fd55416d..858ac182 100644 --- a/app/lib/bloc/settings.dart +++ b/app/lib/bloc/settings.dart @@ -63,7 +63,7 @@ class SetonixSettings with SetonixSettingsMappable implements LeapSettings { @override final bool nativeTitleBar; final bool stackedCards; - final bool showConnectYour, showConnectNetwork; + final bool showConnectYour, showConnectBrowse; final GameProperty gameProperty; final List servers; final double zoom; @@ -81,7 +81,7 @@ class SetonixSettings with SetonixSettingsMappable implements LeapSettings { this.dataDirectory = '', this.nativeTitleBar = false, this.showConnectYour = true, - this.showConnectNetwork = true, + this.showConnectBrowse = false, this.lastVersion, this.gameProperty = const GameProperty(), this.servers = const [], @@ -112,7 +112,7 @@ class SetonixSettings with SetonixSettingsMappable implements LeapSettings { nativeTitleBar: prefs.getBool('nativeTitleBar') ?? false, localeTag: prefs.getString('locale') ?? '', showConnectYour: prefs.getBool('showConnectYour') ?? true, - showConnectNetwork: prefs.getBool('showConnectNetwork') ?? true, + showConnectBrowse: prefs.getBool('showConnectBrowse') ?? false, lastVersion: prefs.getString('lastVersion'), gameProperty: prefs.containsKey('gameProperty') ? GamePropertyMapper.fromJson(prefs.getString('gameProperty')!) @@ -142,7 +142,7 @@ class SetonixSettings with SetonixSettingsMappable implements LeapSettings { await prefs.setBool('nativeTitleBar', nativeTitleBar); await prefs.setString('locale', localeTag); await prefs.setBool('showConnectYour', showConnectYour); - await prefs.setBool('showConnectNetwork', showConnectNetwork); + await prefs.setBool('showConnectBrowse', showConnectBrowse); if (lastVersion == null) { if (prefs.containsKey('last_version')) { await prefs.remove('last_version'); @@ -199,8 +199,8 @@ class SettingsCubit extends Cubit return save(); } - Future changeShowConnectNetwork(bool value) { - emit(state.copyWith(showConnectNetwork: value)); + Future changeShowConnectBrowse(bool value) { + emit(state.copyWith(showConnectBrowse: value)); return save(); } diff --git a/app/lib/bloc/settings.mapper.dart b/app/lib/bloc/settings.mapper.dart index 19eae421..33f36fdc 100644 --- a/app/lib/bloc/settings.mapper.dart +++ b/app/lib/bloc/settings.mapper.dart @@ -104,9 +104,9 @@ class SetonixSettingsMapper extends ClassMapperBase { static bool _$showConnectYour(SetonixSettings v) => v.showConnectYour; static const Field _f$showConnectYour = Field('showConnectYour', _$showConnectYour, opt: true, def: true); - static bool _$showConnectNetwork(SetonixSettings v) => v.showConnectNetwork; - static const Field _f$showConnectNetwork = - Field('showConnectNetwork', _$showConnectNetwork, opt: true, def: true); + static bool _$showConnectBrowse(SetonixSettings v) => v.showConnectBrowse; + static const Field _f$showConnectBrowse = + Field('showConnectBrowse', _$showConnectBrowse, opt: true, def: false); static String? _$lastVersion(SetonixSettings v) => v.lastVersion; static const Field _f$lastVersion = Field('lastVersion', _$lastVersion, opt: true); @@ -150,7 +150,7 @@ class SetonixSettingsMapper extends ClassMapperBase { #dataDirectory: _f$dataDirectory, #nativeTitleBar: _f$nativeTitleBar, #showConnectYour: _f$showConnectYour, - #showConnectNetwork: _f$showConnectNetwork, + #showConnectBrowse: _f$showConnectBrowse, #lastVersion: _f$lastVersion, #gameProperty: _f$gameProperty, #servers: _f$servers, @@ -172,7 +172,7 @@ class SetonixSettingsMapper extends ClassMapperBase { dataDirectory: data.dec(_f$dataDirectory), nativeTitleBar: data.dec(_f$nativeTitleBar), showConnectYour: data.dec(_f$showConnectYour), - showConnectNetwork: data.dec(_f$showConnectNetwork), + showConnectBrowse: data.dec(_f$showConnectBrowse), lastVersion: data.dec(_f$lastVersion), gameProperty: data.dec(_f$gameProperty), servers: data.dec(_f$servers), @@ -252,7 +252,7 @@ abstract class SetonixSettingsCopyWith<$R, $In extends SetonixSettings, $Out> String? dataDirectory, bool? nativeTitleBar, bool? showConnectYour, - bool? showConnectNetwork, + bool? showConnectBrowse, String? lastVersion, GameProperty? gameProperty, List? servers, @@ -300,7 +300,7 @@ class _SetonixSettingsCopyWithImpl<$R, $Out> String? dataDirectory, bool? nativeTitleBar, bool? showConnectYour, - bool? showConnectNetwork, + bool? showConnectBrowse, Object? lastVersion = $none, GameProperty? gameProperty, List? servers, @@ -319,7 +319,7 @@ class _SetonixSettingsCopyWithImpl<$R, $Out> if (dataDirectory != null) #dataDirectory: dataDirectory, if (nativeTitleBar != null) #nativeTitleBar: nativeTitleBar, if (showConnectYour != null) #showConnectYour: showConnectYour, - if (showConnectNetwork != null) #showConnectNetwork: showConnectNetwork, + if (showConnectBrowse != null) #showConnectBrowse: showConnectBrowse, if (lastVersion != $none) #lastVersion: lastVersion, if (gameProperty != null) #gameProperty: gameProperty, if (servers != null) #servers: servers, @@ -340,8 +340,8 @@ class _SetonixSettingsCopyWithImpl<$R, $Out> dataDirectory: data.get(#dataDirectory, or: $value.dataDirectory), nativeTitleBar: data.get(#nativeTitleBar, or: $value.nativeTitleBar), showConnectYour: data.get(#showConnectYour, or: $value.showConnectYour), - showConnectNetwork: - data.get(#showConnectNetwork, or: $value.showConnectNetwork), + showConnectBrowse: + data.get(#showConnectBrowse, or: $value.showConnectBrowse), lastVersion: data.get(#lastVersion, or: $value.lastVersion), gameProperty: data.get(#gameProperty, or: $value.gameProperty), servers: data.get(#servers, or: $value.servers), diff --git a/app/lib/pages/home/connect.dart b/app/lib/pages/home/connect.dart index f4619fe5..62bd9486 100644 --- a/app/lib/pages/home/connect.dart +++ b/app/lib/pages/home/connect.dart @@ -149,7 +149,7 @@ class _ServersDialogState extends State { _servers = ValueConnectableStream(context .read() .fetchServersWithProperties( - browsable: settings.showConnectNetwork, + browsable: settings.showConnectBrowse, local: settings.showConnectYour)) .autoConnect(); } @@ -204,10 +204,10 @@ class _ServersDialogState extends State { listenWhen: (previous, current) => previous.serverList != current.serverList || previous.showConnectYour != current.showConnectYour || - previous.showConnectNetwork != current.showConnectNetwork, + previous.showConnectBrowse != current.showConnectBrowse, buildWhen: (previous, current) => previous.showConnectYour != current.showConnectYour || - previous.showConnectNetwork != current.showConnectNetwork, + previous.showConnectBrowse != current.showConnectBrowse, listener: (context, state) => _refreshServers(state), builder: (context, settings) => ResponsiveAlertDialog( title: Text(AppLocalizations.of(context).servers), @@ -447,11 +447,11 @@ class _ServersDialogState extends State { label: Text(AppLocalizations.of(context).browse), avatar: const Icon(PhosphorIconsLight.globe), showCheckmark: false, - selected: settings.showConnectNetwork, + selected: settings.showConnectBrowse, onPressed: () => context .read() - .changeShowConnectNetwork( - !settings.showConnectNetwork), + .changeShowConnectBrowse( + !settings.showConnectBrowse), ), ]), const SizedBox(height: 8), diff --git a/servers/astro.config.mjs b/servers/astro.config.mjs index a1eef492..c45a1272 100644 --- a/servers/astro.config.mjs +++ b/servers/astro.config.mjs @@ -11,6 +11,11 @@ export default defineConfig({ access: "secret", optional: true, }), + USE_REMOTE_URLS_STATUS: envField.boolean({ + context: "server", + access: "secret", + default: true, + }), }, }, }); diff --git a/servers/src/scripts/servers/info.ts b/servers/src/scripts/servers/info.ts index d9d3966c..bcc00c21 100644 --- a/servers/src/scripts/servers/info.ts +++ b/servers/src/scripts/servers/info.ts @@ -1,5 +1,7 @@ import NodeCache from "@cacheable/node-cache"; import { buildServerURL, type Server } from "./utils"; +import { getRemoteServerStatus } from "./list"; +import { USE_REMOTE_URLS_STATUS } from "astro:env/server"; const cache = new NodeCache({ stdTTL: 60 * 10 }); const failedTTL = 60 * 5; // 5 minutes for failed requests @@ -9,32 +11,42 @@ export type ServerStatus = { maxPlayers?: number; currentPlayers: number; packsSignature: Record; -} +}; -export async function fetchServerStatus(server: Server) : Promise { +export async function fetchServerStatus( + server: Server, +): Promise { + if (server.remote && USE_REMOTE_URLS_STATUS) { + return getRemoteServerStatus(server); + } const cacheKey = `server-status:${server.address}`; const cached = cache.get(cacheKey); if (cached) { return Promise.resolve(cached as ServerStatus); } const url = buildServerURL(server); - const response = await fetch(url, { + const response = (await fetch(url, { method: "GET", headers: { "Content-Type": "application/json", - "X-Setonix-Method": "info" + "X-Setonix-Method": "info", }, }) .then((res) => { if (!res.ok) { - throw new Error(`Server ${server.address} returned status ${res.status}`); + throw new Error( + `Server ${server.address} returned status ${res.status}` + ); } return res.json(); }) .catch((error) => { - console.error(`Error fetching status for server ${server.address}:`, error); + console.error( + `Error fetching status for server ${server.address}:`, + error + ); return null; - }) as ServerStatus | null; + })) as ServerStatus | null; cache.set(cacheKey, response, response ? undefined : failedTTL); if (!response) { console.warn(`Invalid response from server ${server.address}:`, response); diff --git a/servers/src/scripts/servers/list.ts b/servers/src/scripts/servers/list.ts index f3d82c2f..b37ca56d 100644 --- a/servers/src/scripts/servers/list.ts +++ b/servers/src/scripts/servers/list.ts @@ -1,27 +1,32 @@ -import {NodeCache} from '@cacheable/node-cache'; -import { promises as fs } from 'fs'; -import type { Server } from './utils'; -import { REMOTE_URLS } from "astro:env/server"; +import { NodeCache } from "@cacheable/node-cache"; +import { promises as fs } from "fs"; +import type { Server } from "./utils"; +import { REMOTE_URLS, USE_REMOTE_URLS_STATUS } from "astro:env/server"; +import type { ServerStatus } from "./info"; // Cache with 5 minutes TTL const cache = new NodeCache({ stdTTL: 60 * 10 }); // Cache for local servers list let localCache: Server[] | null = null; +export async function getRemoteServerStatus(server: Server) { + return ((server as any)["status"] as ServerStatus) || null; +} + /** * Loads the local servers.json and merges it with all remote lists. */ - export async function loadServers( - localPath: string, - remoteUrls: string[] = [] - ): Promise { +export async function loadServers( + localPath: string, + remoteUrls: string[] = [] +): Promise { if (!localCache) { - const localData = await fs.readFile(localPath, 'utf-8'); + const localData = await fs.readFile(localPath, "utf-8"); localCache = JSON.parse(localData); } const local = localCache!; - async function fetchRemote(url: string): Promise { + async function fetchRemote(url: string): Promise { const cacheKey = `servers:${url}`; const cached = cache.get(cacheKey); if (cached) return cached; @@ -34,11 +39,14 @@ let localCache: Server[] | null = null; } const data = await res.json(); - if (!('servers' in data)) { + if (!("servers" in data)) { console.warn(`Invalid data format from ${url}:`, data); return []; } - const servers = data['servers'] as Server[]; + const servers = data["servers"].map((e: any) => ({ + remote: true, + ...e, + })) as Server[]; cache.set(cacheKey, servers); return servers; } @@ -49,10 +57,10 @@ let localCache: Server[] | null = null; } export async function loadServersFromConfig() { - const localPath = 'src/data/servers.json'; - const remotes = REMOTE_URLS?.split(','); + const localPath = "src/data/servers.json"; + const remotes = REMOTE_URLS?.split(","); return await loadServers( localPath, - remotes?.filter(url => url.trim() !== '') || [] + remotes?.filter((url) => url.trim() !== "") || [] ); } diff --git a/servers/src/scripts/servers/utils.ts b/servers/src/scripts/servers/utils.ts index 52fba529..630f03b2 100644 --- a/servers/src/scripts/servers/utils.ts +++ b/servers/src/scripts/servers/utils.ts @@ -4,6 +4,7 @@ export const ServerObject = z.object({ address: z.string().url(), secure: z.boolean().optional(), highlighted: z.boolean().optional(), + remote: z.boolean().optional(), }); export type Server = z.infer; From 231a7bc321831fb641e513030a4e1eb9bac0cb7d Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Mon, 16 Jun 2025 15:44:55 +0200 Subject: [PATCH 17/18] Add deploy --- .github/workflows/deploy.yml | 66 ++++++++++++---- servers/astro.config.mjs | 9 ++- servers/package.json | 1 + servers/pnpm-lock.yaml | 144 +++++++++++++++++++++++++++++++++++ 4 files changed, 205 insertions(+), 15 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index cbc6f485..52c4dee1 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -8,7 +8,7 @@ on: workflow_dispatch: jobs: - build: + build-docs: runs-on: ubuntu-latest defaults: run: @@ -35,14 +35,14 @@ jobs: SFTP_HOST: ${{ secrets.SFTP_HOST }} SFTP_USERNAME: ${{ secrets.SFTP_USERNAME }} SFTP_KEY: ${{ secrets.SFTP_KEY }} - SFTP_KNOWN_HOSTS: ${{secrets.SFTP_KNOWN_HOSTS}} + SFTP_KNOWN_HOSTS: ${{ secrets.SFTP_KNOWN_HOSTS }} run: | echo "$SFTP_KEY" > sftp_key chmod 600 sftp_key echo "$SFTP_KNOWN_HOSTS" > known_hosts chmod 600 known_hosts rsync -avz --delete -e "ssh -i sftp_key -o UserKnownHostsFile=known_hosts" dist/ $SFTP_USERNAME@$SFTP_HOST:/var/www/www.setonix - doc: + build-doc-api: runs-on: ubuntu-24.04 defaults: run: @@ -54,12 +54,10 @@ jobs: with: flutter-version-file: app/pubspec.yaml - name: Install dependencies - run: | - flutter pub get + run: flutter pub get - name: Generate documentation - run: | - dart doc - - name: Deploy to SFTP + run: dart doc + - name: Deploy API docs via SFTP if: github.ref == 'refs/heads/develop' env: SFTP_HOST: ${{ secrets.SFTP_HOST }} @@ -72,7 +70,7 @@ jobs: echo "$SFTP_KNOWN_HOSTS" > known_hosts chmod 600 known_hosts rsync -avz --delete -e "ssh -i sftp_key -o UserKnownHostsFile=known_hosts" doc/api/ $SFTP_USERNAME@$SFTP_HOST:/var/www/api.setonix - web: + build-web: runs-on: ubuntu-24.04 defaults: run: @@ -84,9 +82,8 @@ jobs: with: flutter-version-file: app/pubspec.yaml - name: Install dependencies - run: | - flutter pub get - - name: Generate + run: flutter pub get + - name: Generate code run: | cd ../tools dart pub get @@ -104,16 +101,57 @@ jobs: echo "WEB_DIR=web" >> $GITHUB_ENV - name: Build run: flutter build web --wasm --release --no-web-resources-cdn --dart-define=flavor=$SETONIX_FLAVOR - - name: Deploy to SFTP + - name: Deploy Flutter web via SFTP if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main' env: SFTP_HOST: ${{ secrets.SFTP_HOST }} SFTP_USERNAME: ${{ secrets.SFTP_USERNAME }} SFTP_KEY: ${{ secrets.SFTP_KEY }} - SFTP_KNOWN_HOSTS: ${{secrets.SFTP_KNOWN_HOSTS}} + SFTP_KNOWN_HOSTS: ${{ secrets.SFTP_KNOWN_HOSTS }} run: | echo "$SFTP_KEY" > sftp_key chmod 600 sftp_key echo "$SFTP_KNOWN_HOSTS" > known_hosts chmod 600 known_hosts rsync -avz --delete -e "ssh -i sftp_key -o UserKnownHostsFile=known_hosts" build/web/ $SFTP_USERNAME@$SFTP_HOST:/var/www/$WEB_DIR.setonix + build-server: + runs-on: ubuntu-latest + defaults: + run: + working-directory: servers + steps: + - name: ⬆️ Checkout + uses: actions/checkout@v4 + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: "pnpm" + cache-dependency-path: docs/pnpm-lock.yaml + - name: Install dependencies + run: pnpm install + - name: Build Astro SSR + run: pnpm build + - name: Setup SSH Agent + if: github.ref == 'refs/heads/develop' + uses: webfactory/ssh-agent@v0.9.1 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + - name: Add SSH Known Hosts + if: github.ref == 'refs/heads/develop' + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_KNOWN_HOSTS }}" > ~/.ssh/known_hosts + + - name: Deploy SSR build via rsync + if: github.ref == 'refs/heads/develop' + run: | + rsync -avz --delete ./dist/ ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }}:/var/www/servers.setonix + + - name: Restart SSR application + if: github.ref == 'refs/heads/develop' + run: | + ssh ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }} << 'EOF' + pm2 stop astro-servers || true + pm2 start dist/server/entry.mjs --name astro-servers + EOF diff --git a/servers/astro.config.mjs b/servers/astro.config.mjs index c45a1272..4639bb97 100644 --- a/servers/astro.config.mjs +++ b/servers/astro.config.mjs @@ -1,9 +1,12 @@ // @ts-check import { defineConfig, envField } from "astro/config"; +import node from "@astrojs/node"; + // https://astro.build/config export default defineConfig({ output: "server", + env: { schema: { REMOTE_URLS: envField.string({ @@ -18,4 +21,8 @@ export default defineConfig({ }), }, }, -}); + + adapter: node({ + mode: "standalone", + }), +}); \ No newline at end of file diff --git a/servers/package.json b/servers/package.json index d97ef6a5..252f2fde 100644 --- a/servers/package.json +++ b/servers/package.json @@ -10,6 +10,7 @@ }, "packageManager": "pnpm@10.12.1", "dependencies": { + "@astrojs/node": "^9.2.2", "@cacheable/node-cache": "^1.5.6", "@linwooddev/style": "^0.4.2", "astro": "^5.9.3" diff --git a/servers/pnpm-lock.yaml b/servers/pnpm-lock.yaml index 0906b2d0..1857865f 100644 --- a/servers/pnpm-lock.yaml +++ b/servers/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@astrojs/node': + specifier: ^9.2.2 + version: 9.2.2(astro@5.9.3(@types/node@24.0.1)(rollup@4.43.0)(typescript@5.8.3)) '@cacheable/node-cache': specifier: ^1.5.6 version: 1.5.6 @@ -29,6 +32,11 @@ packages: '@astrojs/markdown-remark@6.3.2': resolution: {integrity: sha512-bO35JbWpVvyKRl7cmSJD822e8YA8ThR/YbUsciWNA7yTcqpIAL2hJDToWP5KcZBWxGT6IOdOkHSXARSNZc4l/Q==} + '@astrojs/node@9.2.2': + resolution: {integrity: sha512-PtLPuuojmcl9O3CEvXqL/D+wB4x5DlbrGOvP0MeTAh/VfKFprYAzgw1+45xsnTO+QvPWb26l1cT+ZQvvohmvMw==} + peerDependencies: + astro: ^5.3.0 + '@astrojs/prism@3.3.0': resolution: {integrity: sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==} engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} @@ -661,6 +669,10 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -696,12 +708,19 @@ packages: resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} engines: {node: '>=4'} + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + emoji-regex@10.4.0: resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + entities@6.0.1: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} @@ -714,6 +733,9 @@ packages: engines: {node: '>=18'} hasBin: true + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@5.0.0: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} @@ -724,6 +746,10 @@ packages: estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} @@ -751,6 +777,10 @@ packages: fontkit@2.0.4: resolution: {integrity: sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==} + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -808,12 +838,19 @@ packages: http-cache-semantics@4.2.0: resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} import-meta-resolve@4.1.0: resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} @@ -998,6 +1035,14 @@ packages: micromark@4.0.2: resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.1: + resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} + engines: {node: '>= 0.6'} + mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} @@ -1042,6 +1087,10 @@ packages: ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + oniguruma-parser@0.12.1: resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} @@ -1104,6 +1153,10 @@ packages: radix3@1.1.2: resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -1170,6 +1223,16 @@ packages: engines: {node: '>=10'} hasBin: true + send@1.2.0: + resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} + engines: {node: '>= 18'} + + server-destroy@1.0.1: + resolution: {integrity: sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + sharp@0.33.5: resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1194,6 +1257,14 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -1223,6 +1294,10 @@ packages: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -1510,6 +1585,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@astrojs/node@9.2.2(astro@5.9.3(@types/node@24.0.1)(rollup@4.43.0)(typescript@5.8.3))': + dependencies: + '@astrojs/internal-helpers': 0.6.1 + astro: 5.9.3(@types/node@24.0.1)(rollup@4.43.0)(typescript@5.8.3) + send: 1.2.0 + server-destroy: 1.0.1 + transitivePeerDependencies: + - supports-color + '@astrojs/prism@3.3.0': dependencies: prismjs: 1.30.0 @@ -2094,6 +2178,8 @@ snapshots: defu@6.1.4: {} + depd@2.0.0: {} + dequal@2.0.3: {} destr@2.0.5: {} @@ -2119,10 +2205,14 @@ snapshots: dset@3.1.4: {} + ee-first@1.1.1: {} + emoji-regex@10.4.0: {} emoji-regex@8.0.0: {} + encodeurl@2.0.0: {} + entities@6.0.1: {} es-module-lexer@1.7.0: {} @@ -2155,6 +2245,8 @@ snapshots: '@esbuild/win32-ia32': 0.25.5 '@esbuild/win32-x64': 0.25.5 + escape-html@1.0.3: {} + escape-string-regexp@5.0.0: {} estree-walker@2.0.2: {} @@ -2163,6 +2255,8 @@ snapshots: dependencies: '@types/estree': 1.0.8 + etag@1.8.1: {} + eventemitter3@5.0.1: {} extend@3.0.2: {} @@ -2192,6 +2286,8 @@ snapshots: unicode-properties: 1.4.1 unicode-trie: 2.0.0 + fresh@2.0.0: {} + fsevents@2.3.3: optional: true @@ -2306,10 +2402,20 @@ snapshots: http-cache-semantics@4.2.0: {} + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + ieee754@1.2.1: {} import-meta-resolve@4.1.0: {} + inherits@2.0.4: {} + iron-webcrypto@1.2.1: {} is-arrayish@0.3.2: @@ -2670,6 +2776,12 @@ snapshots: transitivePeerDependencies: - supports-color + mime-db@1.54.0: {} + + mime-types@3.0.1: + dependencies: + mime-db: 1.54.0 + mrmime@2.0.1: {} ms@2.1.3: {} @@ -2700,6 +2812,10 @@ snapshots: ohash@2.0.11: {} + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + oniguruma-parser@0.12.1: {} oniguruma-to-es@4.3.3: @@ -2761,6 +2877,8 @@ snapshots: radix3@1.1.2: {} + range-parser@1.2.1: {} + readdirp@4.1.2: {} regex-recursion@6.0.2: @@ -2894,6 +3012,26 @@ snapshots: semver@7.7.2: {} + send@1.2.0: + dependencies: + debug: 4.4.1 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.0 + mime-types: 3.0.1 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + server-destroy@1.0.1: {} + + setprototypeof@1.2.0: {} + sharp@0.33.5: dependencies: color: 4.2.3 @@ -2945,6 +3083,10 @@ snapshots: space-separated-tokens@2.0.2: {} + statuses@2.0.1: {} + + statuses@2.0.2: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -2979,6 +3121,8 @@ snapshots: fdir: 6.4.6(picomatch@4.0.2) picomatch: 4.0.2 + toidentifier@1.0.1: {} + tr46@0.0.3: {} trim-lines@3.0.1: {} From e02a5e8e18a9fd34cdae127743d8fa487799e761 Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Mon, 16 Jun 2025 15:52:12 +0200 Subject: [PATCH 18/18] Fix deplyoing servers --- .github/workflows/deploy.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 52c4dee1..abce6688 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -114,14 +114,17 @@ jobs: echo "$SFTP_KNOWN_HOSTS" > known_hosts chmod 600 known_hosts rsync -avz --delete -e "ssh -i sftp_key -o UserKnownHostsFile=known_hosts" build/web/ $SFTP_USERNAME@$SFTP_HOST:/var/www/$WEB_DIR.setonix - build-server: + build-servers: runs-on: ubuntu-latest defaults: run: working-directory: servers steps: - - name: ⬆️ Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v4 + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + package_json_file: docs/package.json - name: Use Node.js uses: actions/setup-node@v4 with: @@ -130,7 +133,7 @@ jobs: cache-dependency-path: docs/pnpm-lock.yaml - name: Install dependencies run: pnpm install - - name: Build Astro SSR + - name: Build run: pnpm build - name: Setup SSH Agent if: github.ref == 'refs/heads/develop' @@ -153,5 +156,5 @@ jobs: run: | ssh ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }} << 'EOF' pm2 stop astro-servers || true - pm2 start dist/server/entry.mjs --name astro-servers + PORT=${{ secrets.SERVERS_PORT }} pm2 start dist/server/entry.mjs --name astro-servers --update-env EOF