commit 9788479ae765c9928f69c095153531720dec6194 Author: Fox Date: Tue Jun 23 17:47:07 2026 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0808c4a --- /dev/null +++ b/.gitignore @@ -0,0 +1,482 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from `dotnet new gitignore` + +# dotenv files +.env + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +# but not Directory.Build.rsp, as it configures directory-level build defaults +!Directory.Build.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +.idea/ + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Vim temporary swap files +*.swp diff --git a/README.md b/README.md new file mode 100644 index 0000000..364fc86 --- /dev/null +++ b/README.md @@ -0,0 +1,113 @@ +# README +This repo contains Fox's submission for InfoTrack's development project. The backend (found in `/backend`) is a dotnet 10 minimal api, and the frontend is a single-page webapp made using preact and vite. + +A sample of this project will be deployed [here](http://solicitors.starfahx.com) for the next few weeks, should the steps [below](#running-the-project) not work on your machine. + +## Using the App +The frontend project provides a quick way to search through the cached data. The list of firms can be filtered by name (case-insensitive, matches any part of the name) to quickly find a desired firm. Additional fields allow the user to filter results by: +- City: this field is a multi-select, and will return firms in any of the cities selected; if none are selected, it returns firms from all cities. +- Rating: this field allows the user to select a minimum rating, and a rating provider to filter by. Only firms who have a rating provided by the selected provider will be returned, unless the minimum rating field is de-selected. + +These fields are disabled if the option to search by name only is selected. + +Finally, the user can change the ordering of results and the number displayed per page. + +Clicking on a result brings up a modal display of all the relevant ratings and contact information available for the firm. + +## Running the Project +The project is designed to be run using `docker compose`. Clone the project, and add a `compose.yaml` file in the root directory. A sample compose file might look like this: + +```yaml +services: + frontend: + build: + context: ./frontend + args: + VITE_API_URL: http://localhost:5123 + ports: + - "5124:80" + + backend: + build: + context: ./backend + ports: + - "5123:8080" + volumes: + - "/path/to/data:/data" + +``` + +The backend container requires a volume mapped to `/data`, which contains an `apiSettings.json` configuration file, necessary for configuring the api. A sample config file might look like this: + +```json +{ + "Database": { + "Type": "sqlite", + "DbPath": "/data/solicitors.db" + }, + "Imports": { + "ImportMinutes": 5, + "RemoveMinutes": 120, + "StaleMinutes": 60 + } +} +``` + +The `Database` node must contain a `Type` field, which must have the value `"sqlite"` or `"inmemory"`, to configure a database of the appropriate kind. + +For a Sqlite database, the field `"DbPath"`specifies the path to the .db file. It is recommended to choose a file in `/data`, as this is a volume already mounted and can be examined outside the container. + +For an In Memory database, the field `"DbName"` specifies the name to give to the database. + +The `Imports` node must contain the following fields: +- `"ImportMinutes"`: an integer number of minutes on which to run the cache import job. The target website, solicitors.com, does not provide consistent search results, so frequent searches will ensure the most complete cache possible. +- `"RemoveMinutes"`: an integer number of minutes on which to run the cache cleanup job. This job removes stale items from the cache. It is recommended that this job run much less often than the import, for the reason above. +- `"StaleMinutes"`: an integer number of minutes since modified beyond which a cached item is considered stale, and removed by the cleanup job. + +With both the configuration file and the compose file in place, running `docker compose up -d` will start the containerised applications. They can be shut down with `docker compose down`. If you've pulled a newer version of the source code, `docker compose up -d --build` should be used to relaunch the applications. + +## Additional Notes on Implementation +I _somewhat_ exceeded the 1-2 hours mentioned in the document, if the amount of code here + my aversion to AI doesn't make that clear, but given in the interview it was suggested that this could take a couple of weeks I figured that was a guideline and since I didn't have much better to do for this last week I decided to put a bit of effort in. Hope that's okay! It's taken roughly a workweek to get this together, excluding time spent filing bug reports with Fresh (my first choice for a frontend framework). + +I've written some notes on things I've enjoyed doing + want to show off, as well as things that have suffered for the time constraints. I then looked at how much I wrote and realised it was rather a lot. Sorry! Feel free to skim it. + +### HTML Parsing +For the web-scraping part of this project, I've done my best to avoid third-party libraries as requested. To achieve this, I've written an _extremely limited_ HTML parsing library, as I wasn't able to find an easy equivalent in the standard libraries for dotnet. This library uses a design I've borrowed from compiler design (I'm currently working my way through a textbook on compiler design so that was quite handy!) where first the HTML is lexed into tokens and then those tokens are parsed in a pseudo-grammar to form a nested tree of nodes. + +I made use of a C# feature here that I made use of while working through that textbook: by leveraging deferred execution, I'm able to effectively perform a single-pass parsing. I'm not sure this actually has much of a performance improvement, but it seems silly to build up a list of nodes just to immediately enumerate through it again. + +Due to the time constratints, this is really quite messy code and I'm not happy with the api it provides for actually scanning through the HTML tree, but it works for now. I'd love to come back and revisit this at some point, honestly, it was a really fun little project. + +### Cache Store +I've used EFCore as my ORM of choice, because it's super easy to use, and this project finally gave me the opportunity to implement something I've never had an opportunity to do in the past: runtime switching of EFCore providers! I've wanted to build an app that allows the user to define (in a compose file or similar) where the data is stored for a long time, so I'm very glad I got a chance to do that here. + +It's good to know that `EnsureCreatedAsync` works the way I expected for making a new sqlite db, and avoids messing around with migrations too much. Obviously, that's only possible because the schema for this project is pretty static: solving that problem for an actively-developed version of this app, and one that uses a SQL Server or Postgres provider, would probably require a fair bit more engineering. + +### General Architecture +Broadly I try to architect my projects (as can be seen here) into libraries which expose abstractions publicly and keep implementations internal, registering them with a DI container. I think this approach is cleaner than having a bunch of .Abstractions projects lying around when those aren't absolutely necessary! + +The original idea was that the `Solicitors.Core` library would contain service abstractions that could be referenced by libraries for the cache store and for the cache build separately, which seems to have done the trick. This approach got a little confusing at times with the models namespaces, though, so I could probably do a better job of keeping those clean. + +There's also some things that ended up in Core (e.g. the comparers for ordering results) that I don't think really belonged there, which was the result of not really having a place for API-level abstractions to belong. + +Running the imports on a timer within the api is probably not how I'd want to architect this project if it were going to be developed further, but I've gone with that based on the time constraints. In an ideal world there'd be a read-only REST api app + another service app that could be scheduled by the OS. I settled on Quartz.Net to provide scheduling functionality for the import only because I'd heard of it and knew it would work, though I think it's a bit overkill for what I'm trying to do here. + +### API Design +A weak point of this project, which is a shame because it's something I care quite a bit about. I'd love to take the time to do up the API nicely, handle errors + validate input etc etc, but it really seemed quite unnecessary for this project, especially since the API exists only to be consumed by the UI. + +### Frontend Frameworks +As mentioned, I was going to use Deno + Fresh as my frontend framework of choice: I love preact as a lightweight, fast alternative to react, and Fresh using it by default has really won me over. Fresh's approach to server-side rendering is really fun, and I'd spent a while on this project learning all about Fresh's Partials system, which I intended to use for the results display. + +Unfortunately, there's a bug in Fresh that I'd occasionally run into before that means using css/scss modules is deeply inconsistent, which is my go-to pattern if I'm not using a component library. I'm not a big fan of tailwind, and because Deno and Fresh are fairly immature it's quite hard to find a non-tailwind component/styling library that works with them. + +In the end I fell back on regular preact, hosted by vite (not because I know it very well. It's just what Fresh uses under the hood for some things so I thought porting my work would be easy. It wasn't.) I've used MUI as a component library because after losing all that time I just wanted to get it done quickly, and it's a framework I've used before with some success. It's a bit dated but it gets the job done! + +### (Unit) Testing +I really did not have time for this. I _should_ have absolutely done this as TDD, but given I started with the problem of the HTML Parsing and was just writing that out in a scratchpad to make something that even stood a chance of working I ended up skipping that. + +I've added a couple of unit tests more to demonstrate ability and comfort with them than anything else. + +I've done some reasonably thorough manual testing, and pestered friends and family to try to break the site to make sure I hadn't missed anything obvious. I'm pretty confident it'll work! But this is my first time making an app designed to be consumed by `docker compose` so it might just fall apart for you. I've tested deployment both on my desktop and on one of my servers, using various different configurations and on a couple of different Linux distributions. + +## Closing Remarks +Sorry if I took too long over this. It's been nice to have something serious to work on for the last week, so I wanted to enjoy it while it lasted! Thanks for considering me for the role and I hope I get to speak with you soon. \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..4fa5927 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,16 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +WORKDIR /App + +# Copy everything +COPY . ./ +# Restore as distinct layers +RUN dotnet restore +# Build and publish a release +RUN dotnet publish -o out + +# Build runtime image +FROM mcr.microsoft.com/dotnet/aspnet:10.0 +VOLUME /data +WORKDIR /App +COPY --from=build /App/out . +ENTRYPOINT ["dotnet", "Solicitors.Api.dll"] \ No newline at end of file diff --git a/backend/SolicitorsApi.slnx b/backend/SolicitorsApi.slnx new file mode 100644 index 0000000..40ac908 --- /dev/null +++ b/backend/SolicitorsApi.slnx @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/backend/app/Solicitors.Api/Endpoints/CitiesEndpoints.cs b/backend/app/Solicitors.Api/Endpoints/CitiesEndpoints.cs new file mode 100644 index 0000000..76f3cc3 --- /dev/null +++ b/backend/app/Solicitors.Api/Endpoints/CitiesEndpoints.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Mvc; +using Solicitors.Core; + +namespace Solicitors.Api.Endpoints; + +public static class CitiesEndpoints +{ + public static void MapCities(this IEndpointRouteBuilder app) + { + app.MapGet( + "/cities", + ( + [FromServices] IReadOnlyCitiesService citiesService, + CancellationToken cancellationToken + ) => citiesService.GetAllCitiesAsync(cancellationToken)); + } +} \ No newline at end of file diff --git a/backend/app/Solicitors.Api/Endpoints/RatingsProvidersEndpoints.cs b/backend/app/Solicitors.Api/Endpoints/RatingsProvidersEndpoints.cs new file mode 100644 index 0000000..2575281 --- /dev/null +++ b/backend/app/Solicitors.Api/Endpoints/RatingsProvidersEndpoints.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Mvc; +using Solicitors.Core; + +namespace Solicitors.Api.Endpoints; + +public static class RatingsProvidersEndpoints +{ + public static void MapRatingsProviders(this IEndpointRouteBuilder app) + { + app.MapGet( + "/ratingsProviders", + ( + [FromServices] IRatingsProviderService service, + CancellationToken cancellationToken + ) => service.GetRatingsProvidersAsync(cancellationToken)); + } +} \ No newline at end of file diff --git a/backend/app/Solicitors.Api/Endpoints/SolicitorsEndpoints.cs b/backend/app/Solicitors.Api/Endpoints/SolicitorsEndpoints.cs new file mode 100644 index 0000000..6e07503 --- /dev/null +++ b/backend/app/Solicitors.Api/Endpoints/SolicitorsEndpoints.cs @@ -0,0 +1,60 @@ +using Microsoft.AspNetCore.Mvc; +using Solicitors.Core; +using Solicitors.Core.Misc; +using Solicitors.Core.Models; +using Solicitors.Core.Ordering; + +namespace Solicitors.Api.Endpoints; + +public static class SolicitorsEndpoints +{ + public static void MapSolicitors(this IEndpointRouteBuilder app) + { + app.MapGet( + "/solicitors", + ( + [FromServices] IReadOnlySolicitorService solicitorService, + [FromServices] ISolicitorComparerFactory solicitorComparerFactory, + CancellationToken cancellationToken, + [FromQuery] uint pageNumber = 1, + [FromQuery] uint pageSize = 20, + [FromQuery] string? nameFilter = null, + [FromQuery] decimal minRating = 0, + [FromQuery] string[]? cities = null, + [FromQuery] string ratingsProvider = "Solicitors.com", + [FromQuery] OrderingType? orderingType = null) => + { + IFilter? filter = null; + + if (!string.IsNullOrEmpty(nameFilter)) + filter = new WrapperFilter(filter, solicitor => solicitor.Name.Contains(nameFilter, StringComparison.CurrentCultureIgnoreCase)); + + if (cities is not null && cities.Length > 0) + filter = new WrapperFilter(filter, solicitor => cities.Any(city => solicitor.Cities.Any(solCity => solCity.Name == city))); + + if (minRating > 0) + filter = new WrapperFilter( + filter, + solicitor => solicitor.Ratings.Any(rating => rating.Provider == ratingsProvider && rating.Value / rating.Maximum >= minRating / 5.0m)); + + var ordering = solicitorComparerFactory.GetComparer(orderingType, ratingsProvider); + + return solicitorService.GetSolicitorSummariesAsync( + new Pagination(pageNumber, pageSize), + ratingsProvider, + filter, + ordering, + cancellationToken: cancellationToken); + } + ); + + app.MapGet( + "/solicitors/{id:guid}", + ( + [FromServices] IReadOnlySolicitorService solicitorService, + [FromRoute] Guid id, + CancellationToken cancellationToken + ) => solicitorService.GetSolicitorInfoByIdAsync(id, cancellationToken) + ); + } +} \ No newline at end of file diff --git a/backend/app/Solicitors.Api/Program.cs b/backend/app/Solicitors.Api/Program.cs new file mode 100644 index 0000000..774a2af --- /dev/null +++ b/backend/app/Solicitors.Api/Program.cs @@ -0,0 +1,42 @@ +using Solicitors.Api.Endpoints; +using Solicitors.CacheBuild; +using Solicitors.Core; +using Solicitors.Data; +using Solicitors.HtmlParsing; + +var builder = WebApplication.CreateBuilder(args); + +builder.Configuration.AddJsonFile("/data/apiSettings.json", optional: true); + +builder.Services.AddOpenApi(); +builder.Services.AddCore(); +builder.Services.AddData(builder.Configuration); +builder.Services.AddCacheBuild(); +builder.Services.Configure(builder.Configuration.GetSection("Imports")); +builder.Services.AddHtmlParsing(); +builder.Services.AddHttpClient(); +builder.Services.AddCors(options => +{ + options.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin()); +}); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.UseHttpsRedirection(); +app.MapSolicitors(); +app.MapCities(); +app.MapRatingsProviders(); +app.UseCors(); + +using (var scope = app.Services.CreateScope()) +{ + await scope.UseCacheBuildAsync(CancellationToken.None); +} + +app.Run(); \ No newline at end of file diff --git a/backend/app/Solicitors.Api/Properties/launchSettings.json b/backend/app/Solicitors.Api/Properties/launchSettings.json new file mode 100644 index 0000000..9ac83b5 --- /dev/null +++ b/backend/app/Solicitors.Api/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5254", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7226;http://localhost:5254", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/backend/app/Solicitors.Api/Solicitors.Api.csproj b/backend/app/Solicitors.Api/Solicitors.Api.csproj new file mode 100644 index 0000000..4765b66 --- /dev/null +++ b/backend/app/Solicitors.Api/Solicitors.Api.csproj @@ -0,0 +1,22 @@ + + + + net10.0 + enable + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/backend/app/Solicitors.Api/Solicitors.Api.http b/backend/app/Solicitors.Api/Solicitors.Api.http new file mode 100644 index 0000000..a8e024f --- /dev/null +++ b/backend/app/Solicitors.Api/Solicitors.Api.http @@ -0,0 +1,6 @@ +@Solicitors.Api_HostAddress = http://localhost:5254 + +GET {{Solicitors.Api_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/backend/app/Solicitors.Api/appsettings.json b/backend/app/Solicitors.Api/appsettings.json new file mode 100644 index 0000000..149cb09 --- /dev/null +++ b/backend/app/Solicitors.Api/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/backend/libs/Solicitors.CacheBuild/DIExtensions.cs b/backend/libs/Solicitors.CacheBuild/DIExtensions.cs new file mode 100644 index 0000000..37f832c --- /dev/null +++ b/backend/libs/Solicitors.CacheBuild/DIExtensions.cs @@ -0,0 +1,54 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Quartz; + +namespace Solicitors.CacheBuild; + +[SuppressMessage("ReSharper", "InconsistentNaming")] +public static class DIExtensions +{ + public static IServiceCollection AddCacheBuild(this IServiceCollection services) + { + return services + .AddScoped() + .AddScoped() + .AddQuartz() + .AddQuartzHostedService(opt => opt.WaitForJobsToComplete = true); + } + + public static async Task UseCacheBuildAsync(this IServiceScope scope, CancellationToken cancellationToken) + { + var factory = scope.ServiceProvider.GetRequiredService(); + var options = scope.ServiceProvider.GetRequiredService>().Value; + var scheduler = await factory.GetScheduler(cancellationToken); + + var importJob = JobBuilder.Create() + .WithIdentity("ImportJob", "ImportGroup") + .Build(); + + var importTrigger = TriggerBuilder.Create() + .WithIdentity("ImportRunner", "ImportGroup") + .StartNow() + .WithSimpleSchedule(builder => builder + .WithIntervalInMinutes(options.ImportMinutes) + .RepeatForever()) + .Build(); + + await scheduler.ScheduleJob(importJob, importTrigger, cancellationToken); + + var staleJob = JobBuilder.Create() + .WithIdentity("StaleDataRemover", "RemoveGroup") + .Build(); + + var staleTrigger = TriggerBuilder.Create() + .WithIdentity("StaleDataTrigger", "RemoveGroup") + .StartNow() + .WithSimpleSchedule(builder => builder + .WithIntervalInMinutes(options.RemoveMinutes) + .RepeatForever()) + .Build(); + + await scheduler.ScheduleJob(staleJob, staleTrigger, cancellationToken); + } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.CacheBuild/ISolicitorImporter.cs b/backend/libs/Solicitors.CacheBuild/ISolicitorImporter.cs new file mode 100644 index 0000000..791c72c --- /dev/null +++ b/backend/libs/Solicitors.CacheBuild/ISolicitorImporter.cs @@ -0,0 +1,6 @@ +namespace Solicitors.CacheBuild; + +public interface ISolicitorImporter +{ + Task RunFullImport(CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/backend/libs/Solicitors.CacheBuild/ISolicitorParser.cs b/backend/libs/Solicitors.CacheBuild/ISolicitorParser.cs new file mode 100644 index 0000000..0119a8c --- /dev/null +++ b/backend/libs/Solicitors.CacheBuild/ISolicitorParser.cs @@ -0,0 +1,9 @@ +using Solicitors.Core.Models.Imports; + +namespace Solicitors.CacheBuild; + +internal interface ISolicitorParser +{ + Task GetSolicitorsAsync( + CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/backend/libs/Solicitors.CacheBuild/ImportConfiguration.cs b/backend/libs/Solicitors.CacheBuild/ImportConfiguration.cs new file mode 100644 index 0000000..70a8ef6 --- /dev/null +++ b/backend/libs/Solicitors.CacheBuild/ImportConfiguration.cs @@ -0,0 +1,8 @@ +namespace Solicitors.CacheBuild; + +public class ImportConfiguration +{ + public int StaleMinutes { get; set; } + public int RemoveMinutes { get; set; } + public int ImportMinutes { get; set; } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.CacheBuild/ImportRunner.cs b/backend/libs/Solicitors.CacheBuild/ImportRunner.cs new file mode 100644 index 0000000..72cefb7 --- /dev/null +++ b/backend/libs/Solicitors.CacheBuild/ImportRunner.cs @@ -0,0 +1,15 @@ +using Quartz; +using Solicitors.Core.Data; + +namespace Solicitors.CacheBuild; + +internal class ImportRunner( + ISolicitorImporter importer, + ISolicitorRepository repository) : IJob +{ + public async Task Execute(IJobExecutionContext context) + { + await repository.EnsureCreatedAsync(context.CancellationToken); + await importer.RunFullImport(context.CancellationToken); + } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.CacheBuild/SolicitorImporter.cs b/backend/libs/Solicitors.CacheBuild/SolicitorImporter.cs new file mode 100644 index 0000000..220c074 --- /dev/null +++ b/backend/libs/Solicitors.CacheBuild/SolicitorImporter.cs @@ -0,0 +1,20 @@ +using Solicitors.Core.Data; +using Solicitors.Core.Models.Imports; + +namespace Solicitors.CacheBuild; + +internal class SolicitorImporter( + ISolicitorParser parser, + ISolicitorRepository repository) : ISolicitorImporter +{ + public async Task RunFullImport(CancellationToken cancellationToken) + { + foreach (var solicitor in await parser.GetSolicitorsAsync(cancellationToken)) + { + await RunImport(solicitor, cancellationToken); + } + } + + private Task RunImport(SolicitorData solicitor, CancellationToken cancellationToken) + => repository.AddOrUpdateSolicitorAsync(solicitor, cancellationToken); +} \ No newline at end of file diff --git a/backend/libs/Solicitors.CacheBuild/Solicitors.CacheBuild.csproj b/backend/libs/Solicitors.CacheBuild/Solicitors.CacheBuild.csproj new file mode 100644 index 0000000..7e73086 --- /dev/null +++ b/backend/libs/Solicitors.CacheBuild/Solicitors.CacheBuild.csproj @@ -0,0 +1,27 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + + + + + + + ..\..\..\..\..\..\..\.dotnet\shared\Microsoft.AspNetCore.App\10.0.7\Microsoft.Extensions.Hosting.Abstractions.dll + + + + diff --git a/backend/libs/Solicitors.CacheBuild/SolicitorsDotCom/Consts.cs b/backend/libs/Solicitors.CacheBuild/SolicitorsDotCom/Consts.cs new file mode 100644 index 0000000..b6c3840 --- /dev/null +++ b/backend/libs/Solicitors.CacheBuild/SolicitorsDotCom/Consts.cs @@ -0,0 +1,14 @@ +namespace Solicitors.CacheBuild.SolicitorsDotCom; + +internal static class Consts +{ + public const string BaseUrl = "https://www.solicitors.com"; + public static readonly string[] Locations = + [ + "london", "birmingham", "leeds", "manchester", "sheffield", "bradford", "liverpool", "bristol" + ]; + + public const decimal MaxRating = 5.0m; + public const string Name = "Solicitors.com"; + public const string IconPath = "/images/logo.svg"; +} \ No newline at end of file diff --git a/backend/libs/Solicitors.CacheBuild/SolicitorsDotCom/SolicitorBuilder.cs b/backend/libs/Solicitors.CacheBuild/SolicitorsDotCom/SolicitorBuilder.cs new file mode 100644 index 0000000..d7c290c --- /dev/null +++ b/backend/libs/Solicitors.CacheBuild/SolicitorsDotCom/SolicitorBuilder.cs @@ -0,0 +1,31 @@ +namespace Solicitors.CacheBuild.SolicitorsDotCom; + +internal class SolicitorBuilder +{ + private readonly HashSet _baseLocations = []; + + public SolicitorBuilder( + string name, + string path, + string? shortDesc, + string baseLocation) + { + Name = name; + ShortDescription = shortDesc; + _baseLocations.Add(baseLocation); + + Path = path; + } + + public void AddBaseLocation(string baseLocation) + { + _baseLocations.Add(baseLocation); + } + + public string Path { get; } + public string Name { get; } + + public string? ShortDescription { get; } + + public string[] BaseLocations => _baseLocations.ToArray(); +} \ No newline at end of file diff --git a/backend/libs/Solicitors.CacheBuild/SolicitorsDotCom/SolicitorParser.cs b/backend/libs/Solicitors.CacheBuild/SolicitorsDotCom/SolicitorParser.cs new file mode 100644 index 0000000..b2a6460 --- /dev/null +++ b/backend/libs/Solicitors.CacheBuild/SolicitorsDotCom/SolicitorParser.cs @@ -0,0 +1,315 @@ +using System.Diagnostics.CodeAnalysis; +using Solicitors.Core.Misc; +using Solicitors.Core.Models.Imports; +using Solicitors.HtmlParsing; +using Solicitors.HtmlParsing.Models; + +namespace Solicitors.CacheBuild.SolicitorsDotCom; + +internal class SolicitorParser : ISolicitorParser +{ + private readonly HttpClient _client; + private readonly IHtmlParser _htmlParser; + + public SolicitorParser( + HttpClient client, + IHtmlParser htmlParser) + { + _client = client; + _client.BaseAddress = new Uri(Consts.BaseUrl); + + _htmlParser = htmlParser; + } + + public async Task GetSolicitorsAsync( + CancellationToken cancellationToken = default) + { + var builders = new Dictionary(); + + foreach (var location in Consts.Locations) + { + var httpRequest = new HttpRequestMessage(HttpMethod.Get, $"/conveyancing+{location}.html"); + httpRequest.Headers.Add("User-Agent", "Solicitors API Cache"); + var httpResponse = await _client.SendAsync(httpRequest, cancellationToken); + var html = await httpResponse.Content.ReadAsStringAsync(cancellationToken); + var nodes = _htmlParser.ParseHtml(html); + IHtmlNode? mainNode = null; + foreach (var node in nodes) + if (node.TryGetByTagName("main", out mainNode)) + break; + + if (TryGetResultSection(mainNode, out var results)) + ParseResultsSection(results, location, builders); + } + + var batches = builders.Values + .Select(builder => GetFullSolicitorDataAsync(builder, cancellationToken)) + .Batch(5); + + var solicitors = new List(); + foreach (var batch in batches) + solicitors.AddRange(await Task.WhenAll(batch)); + + return solicitors + .Where(x => x is not null) + .Cast() + .ToArray(); + } + + private bool TryGetResultSection( + IHtmlNode? mainNode, + [NotNullWhen(true)] out IEnumerable? results) + { + results = null; + + // going one level at a time helps a little with performance here, at the cost of messier code + return mainNode is not null + && mainNode.TryGetByTagNameAndClass("div", "content-holder", out var contentHolderNode) + && contentHolderNode.TryGetByTagNameAndClass("div", "container", out var containerNode) + && containerNode.TryGetByTagNameAndClass("div", "content", out var contentNode) + && contentNode.TryGetByTagNameAndClass("div", "result-section", out var resultSectionNode) + && resultSectionNode.TryGetChildren(out results); + } + + private void ParseResultsSection( + IEnumerable results, + string location, + Dictionary builders) + { + foreach (var result in results) + { + if (result.TryGetByClass("result-item", out var resultItem, true) + && TryParseToNewBuilder(resultItem, location, out var builder)) + { + if (!builders.TryAdd(builder.Path, builder)) + builders[builder.Path].AddBaseLocation(location); + } + } + } + + private bool TryParseToNewBuilder( + IHtmlNode resultItemNode, + string baseLocation, + [NotNullWhen(true)] out SolicitorBuilder? builder) + { + builder = null; + if (!resultItemNode.TryGetByTagNameAndClass("a", "link-map", out var linkMapNode) + || !linkMapNode.TryGetAttributeValue("href", out var uniqueUrl) + || !resultItemNode.TryGetByTagNameAndClass("span", "h2", out var titleNode) + || !titleNode.TryGetChildren(out var titleChildren)) + return false; + + string? name = null; + foreach (var child in titleChildren) + if (child.TryGetText(out name)) + break; + + if (name is null) + return false; + + string? shortDesc = null; + if (resultItemNode.TryGetByTagName("p", out var pNode) + && pNode.TryGetChildren(out var pChildren)) + foreach (var child in pChildren) + if (child.TryGetText(out shortDesc)) + break; + + builder = new SolicitorBuilder(name, uniqueUrl, shortDesc, baseLocation); + return true; + } + + private async Task GetFullSolicitorDataAsync( + SolicitorBuilder builder, + CancellationToken cancellationToken) + { + + var httpRequest = new HttpRequestMessage(HttpMethod.Get, builder.Path); + httpRequest.Headers.Add("User-Agent", "Solicitors API Cache"); + var httpResponse = await _client.SendAsync(httpRequest, cancellationToken); + var html = await httpResponse.Content.ReadAsStringAsync(cancellationToken); + var nodes = _htmlParser.ParseHtml(html); + IHtmlNode? mainNode = null; + foreach (var node in nodes) + if (node.TryGetByTagName("main", out mainNode)) + break; + + if (mainNode is null || !mainNode.TryGetByTagNameAndClass("div", "content-block", out var contentBlockNode)) + return null; + + Location[] offices = []; + if (contentBlockNode.TryGetByTagNameAndClass("div", "office-item", out var officeItemNode) + && officeItemNode.TryGetChildren(out var officeItems)) + { + offices = ParseOffices(officeItems); + } + + Rating[] ratings = []; + string? phone = null; + string? email = null; + string? website = null; + if (mainNode.TryGetByTagNameAndClass("div", "side-holder", out var sidebarNode) + && sidebarNode.TryGetChildren(out var sidebarChildren)) + { + ParseSidebar(sidebarChildren, out phone, out email, out website, out ratings); + } + + return new SolicitorData + { + Name = builder.Name, + UrlPath = builder.Path, + ShortDescription = builder.ShortDescription, + Phone = phone, + Email = email, + Website = website, + Ratings = ratings, + Offices = offices, + Cities = builder.BaseLocations + }; + } + + private Location[] ParseOffices(IEnumerable officeNodes) + => officeNodes + .Select(ParseOffice) + .Where(x => x is not null) + .Cast() + .ToArray(); + + private Location? ParseOffice(IHtmlNode officeNode) + { + if (!officeNode.TryGetChildren(out var officeChildren) + || !officeNode.TryGetByTagName("address", out var addressNode) + || !addressNode.TryGetChildren(out var addressLines)) + return null; + + Location? location = null; + var address = ""; + foreach (var line in addressLines) + { + if (line.TryGetText(out var lineText)) + address += lineText; + else if (line.TryGetByTagName("br", out _, true)) + address += "\n"; + } + + if (string.IsNullOrWhiteSpace(address)) + address = null; + + string? phone = null; + foreach (var child in officeChildren) + { + if (child.TryGetByTagName("a", out var anchor) + && anchor.TryGetAttributeValue("href", out var telString) + && telString.StartsWith("\"tel:") + && telString.Length > 5) + { + phone = telString.Substring(5, telString.Length - 6); + break; + } + } + + if (address is not null && phone is not null) + location = new Location(address, phone, []); + + return location; + } + + private void ParseSidebar( + IEnumerable sidebarChildren, + out string? phone, + out string? email, + out string? website, + out Rating[] ratings) + { + List ratingsList = []; + phone = null; + website = null; + email = null; + + foreach (var node in sidebarChildren) + { + var isRevCount = node.TryGetByTagNameAndClass("div", "rev-count", out _, true); + var isLinksHolder = node.TryGetByTagNameAndClass("div", "links-holder", out _, true); + if ((!isRevCount && !isLinksHolder) + || !node.TryGetChildren(out var children)) + continue; + + if (isLinksHolder) + { + if (node.TryGetByTagNameAndClass("a", "website", out var websiteNode) + && websiteNode.TryGetAttributeValue("href", out website)) + website = website.Trim('"'); + + if (node.TryGetByTagNameAndClass("a", "phone", out var phoneNode) + && phoneNode.TryGetAttributeValue("href", out var phoneStr) + && phoneStr.StartsWith("\"tel:") + && phoneStr.Length > 5) + { + phone = phoneStr.Substring(5, phoneStr.Length - 6); + } + } + + var childrenArray = children as IHtmlNode[] ?? children.ToArray(); + foreach (var child in childrenArray.Where(x => x.TryGetByClass("rev-box", out _, true))) + { + if (child.TryGetAttributeValue("title", out var ratingString) + && TryParseRatingString(ratingString, out var rating, out var maxRating) + && child.TryGetByTagNameAndClass("img", "rev-logo", out var imgNode) + && imgNode.TryGetAttributeValue("src", out var imgSrc) + && TryParseRatingSrc(imgSrc, out var ratingProvider)) + { + ratingsList.Add(new Rating(rating, maxRating, ratingProvider, imgSrc)); + } + } + + if (ratingsList.Count == 0) + { + foreach (var child in childrenArray.Where(x => !x.HasAttribute("class"))) + { + if (child.TryGetChildren(out var grandChildren)) + foreach (var grandChild in grandChildren) + if (grandChild.TryGetText(out var textContent) + && TryParseOwnRatingString(textContent, out var rating)) + ratingsList.Add(new Rating(rating, Consts.MaxRating, Consts.Name, Consts.IconPath)); + } + } + } + + ratings = ratingsList.ToArray(); + } + + + bool TryParseRatingString(string ratingString, out decimal rating, out decimal maxRating) + { + rating = 0; + maxRating = 0; + var parts = ratingString.Trim('"').Split(" / "); + if (parts.Length != 2) + return false; + + return decimal.TryParse(parts[0], out rating) && decimal.TryParse(parts[1], out maxRating); + } + + private bool TryParseRatingSrc(string imgSrc, out string provider) + { + provider = ""; + imgSrc = imgSrc.Trim('"'); + const string prefix = "/images/logo-"; + if (!imgSrc.StartsWith(prefix)) + return false; + + var providerAndExtension = imgSrc[prefix.Length..]; + provider = providerAndExtension.Split('.').First(); + return true; + } + + bool TryParseOwnRatingString(string ratingString, out decimal rating) + { + rating = 0; + const string prefix = "Average review score : "; + if (!ratingString.StartsWith(prefix)) + return false; + + var ratingPart = ratingString[prefix.Length..]; + return decimal.TryParse(ratingPart, out rating); + } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.CacheBuild/StaleDataRemover.cs b/backend/libs/Solicitors.CacheBuild/StaleDataRemover.cs new file mode 100644 index 0000000..66684af --- /dev/null +++ b/backend/libs/Solicitors.CacheBuild/StaleDataRemover.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Options; +using Quartz; +using Solicitors.Core.Data; + +namespace Solicitors.CacheBuild; + +internal class StaleDataRemover( + IOptions config, + ISolicitorRepository repository) : IJob +{ + private readonly ImportConfiguration _config = config.Value; + + public Task Execute(IJobExecutionContext context) + { + return repository.RemoveStaleEntriesAsync( + TimeSpan.FromMinutes(_config.StaleMinutes), + context.CancellationToken); + } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/DIExtensions.cs b/backend/libs/Solicitors.Core/DIExtensions.cs new file mode 100644 index 0000000..8111f79 --- /dev/null +++ b/backend/libs/Solicitors.Core/DIExtensions.cs @@ -0,0 +1,19 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.DependencyInjection; +using Solicitors.Core.Ordering; + +namespace Solicitors.Core; + +[SuppressMessage("ReSharper", "InconsistentNaming")] +public static class DIExtensions +{ + public static IServiceCollection AddCore(this IServiceCollection services) + { + return services + .AddScoped() + .AddScoped(sp => sp.GetRequiredService()) + .AddScoped() + .AddScoped() + .AddScoped(); + } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Data/IRatingsProviderRepository.cs b/backend/libs/Solicitors.Core/Data/IRatingsProviderRepository.cs new file mode 100644 index 0000000..1ce39af --- /dev/null +++ b/backend/libs/Solicitors.Core/Data/IRatingsProviderRepository.cs @@ -0,0 +1,6 @@ +namespace Solicitors.Core.Data; + +public interface IRatingsProviderRepository +{ + IAsyncEnumerable GetAllRatingsProvidersAsync(); +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Data/IReadOnlySolicitorRepository.cs b/backend/libs/Solicitors.Core/Data/IReadOnlySolicitorRepository.cs new file mode 100644 index 0000000..d0d2293 --- /dev/null +++ b/backend/libs/Solicitors.Core/Data/IReadOnlySolicitorRepository.cs @@ -0,0 +1,14 @@ +using Solicitors.Core.Models; + +namespace Solicitors.Core.Data; + +public interface IReadOnlySolicitorRepository +{ + IAsyncEnumerable GetAllSolicitorsAsync(); + Task GetSolicitorByIdAsync(Guid id, CancellationToken cancellationToken = default); +} + +public interface IReadOnlyCitiesRepository +{ + IAsyncEnumerable GetCitiesAsync(); +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Data/ISolicitorRepository.cs b/backend/libs/Solicitors.Core/Data/ISolicitorRepository.cs new file mode 100644 index 0000000..a2227e2 --- /dev/null +++ b/backend/libs/Solicitors.Core/Data/ISolicitorRepository.cs @@ -0,0 +1,10 @@ +using Solicitors.Core.Models.Imports; + +namespace Solicitors.Core.Data; + +public interface ISolicitorRepository : IReadOnlySolicitorRepository +{ + Task EnsureCreatedAsync(CancellationToken cancellationToken); + Task AddOrUpdateSolicitorAsync(SolicitorData solicitor, CancellationToken cancellationToken); + Task RemoveStaleEntriesAsync(TimeSpan staleAge, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/IRatingsProviderService.cs b/backend/libs/Solicitors.Core/IRatingsProviderService.cs new file mode 100644 index 0000000..f8fb306 --- /dev/null +++ b/backend/libs/Solicitors.Core/IRatingsProviderService.cs @@ -0,0 +1,6 @@ +namespace Solicitors.Core; + +public interface IRatingsProviderService +{ + Task GetRatingsProvidersAsync(CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/IReadOnlyCitiesService.cs b/backend/libs/Solicitors.Core/IReadOnlyCitiesService.cs new file mode 100644 index 0000000..0322247 --- /dev/null +++ b/backend/libs/Solicitors.Core/IReadOnlyCitiesService.cs @@ -0,0 +1,6 @@ +namespace Solicitors.Core; + +public interface IReadOnlyCitiesService +{ + Task GetAllCitiesAsync(CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/IReadOnlySolicitorService.cs b/backend/libs/Solicitors.Core/IReadOnlySolicitorService.cs new file mode 100644 index 0000000..e2db2fd --- /dev/null +++ b/backend/libs/Solicitors.Core/IReadOnlySolicitorService.cs @@ -0,0 +1,17 @@ +using Solicitors.Core.Misc; +using Solicitors.Core.Models; +using Solicitors.Core.Models.View; + +namespace Solicitors.Core; + +public interface IReadOnlySolicitorService +{ + Task> GetSolicitorSummariesAsync( + Pagination pagination, + string ratingsProvider, + IFilter? filter = null, + IComparer? ordering = null, + CancellationToken cancellationToken = default); + + Task GetSolicitorInfoByIdAsync(Guid id, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/ISolicitorService.cs b/backend/libs/Solicitors.Core/ISolicitorService.cs new file mode 100644 index 0000000..78aa8bb --- /dev/null +++ b/backend/libs/Solicitors.Core/ISolicitorService.cs @@ -0,0 +1,3 @@ +namespace Solicitors.Core; + +public interface ISolicitorService : IReadOnlySolicitorService; \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Misc/EnumerableExtensions.cs b/backend/libs/Solicitors.Core/Misc/EnumerableExtensions.cs new file mode 100644 index 0000000..90be174 --- /dev/null +++ b/backend/libs/Solicitors.Core/Misc/EnumerableExtensions.cs @@ -0,0 +1,21 @@ +namespace Solicitors.Core.Misc; + +public static class EnumerableExtensions +{ + public static IEnumerable Batch(this IEnumerable source, int batchSize) + { + var batch = new List(batchSize); + foreach (var item in source) + { + batch.Add(item); + if (batch.Count == batch.Capacity) + { + yield return batch.ToArray(); + batch.Clear(); + } + } + + if (batch.Count > 0) + yield return batch.ToArray(); + } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Misc/IFilter.cs b/backend/libs/Solicitors.Core/Misc/IFilter.cs new file mode 100644 index 0000000..c12c07b --- /dev/null +++ b/backend/libs/Solicitors.Core/Misc/IFilter.cs @@ -0,0 +1,19 @@ +namespace Solicitors.Core.Misc; + +public interface IFilter +{ + bool Filter(T item); +} + +public class WrapperFilter( + IFilter? inner, + Func filter) : IFilter +{ + public bool Filter(T item) + { + if (inner is not null) + return inner.Filter(item) && filter(item); + + return filter(item); + } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Misc/Pagination.cs b/backend/libs/Solicitors.Core/Misc/Pagination.cs new file mode 100644 index 0000000..ed4168c --- /dev/null +++ b/backend/libs/Solicitors.Core/Misc/Pagination.cs @@ -0,0 +1,7 @@ +namespace Solicitors.Core.Misc; + +public readonly struct Pagination(uint currentPage, uint pageSize) +{ + public uint CurrentPage { get; } = currentPage; + public uint PageSize { get; } = pageSize; +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Models/City.cs b/backend/libs/Solicitors.Core/Models/City.cs new file mode 100644 index 0000000..5ba38e1 --- /dev/null +++ b/backend/libs/Solicitors.Core/Models/City.cs @@ -0,0 +1,10 @@ +namespace Solicitors.Core.Models; + +public record City +{ + public Guid CityId { get; set; } + public required string Name { get; set; } + public DateTime LastModified { get; set; } + + public List Solicitors { get; } = []; +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Models/Imports/Location.cs b/backend/libs/Solicitors.Core/Models/Imports/Location.cs new file mode 100644 index 0000000..e70a309 --- /dev/null +++ b/backend/libs/Solicitors.Core/Models/Imports/Location.cs @@ -0,0 +1,8 @@ +namespace Solicitors.Core.Models.Imports; + +public record Location(string Address, string Phone, Rating[] LocationRatings) +{ + public string Address { get; } = Address; + public string Phone { get; } = Phone; + public Rating[] LocationRatings { get; } = LocationRatings; +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Models/Imports/Rating.cs b/backend/libs/Solicitors.Core/Models/Imports/Rating.cs new file mode 100644 index 0000000..da987e4 --- /dev/null +++ b/backend/libs/Solicitors.Core/Models/Imports/Rating.cs @@ -0,0 +1,9 @@ +namespace Solicitors.Core.Models.Imports; + +public record Rating(decimal Value, decimal MaxValue, string RatingProvider, string RatingProviderImgSrc) +{ + public decimal Value { get; } = Value; + public decimal MaxValue { get; } = MaxValue; + public string RatingProvider { get; } = RatingProvider; + public string RatingProviderImgSrc { get; } = RatingProviderImgSrc; +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Models/Imports/SolicitorData.cs b/backend/libs/Solicitors.Core/Models/Imports/SolicitorData.cs new file mode 100644 index 0000000..e3489e7 --- /dev/null +++ b/backend/libs/Solicitors.Core/Models/Imports/SolicitorData.cs @@ -0,0 +1,16 @@ +namespace Solicitors.Core.Models.Imports; + +public class SolicitorData +{ + public required string Name { get; init; } + public required string UrlPath { get; init; } + + public string? ShortDescription { get; init; } + public string? Phone { get; init; } + public string? Email { get; init; } + public string? Website { get; init; } + + public string[] Cities { get; init; } = []; + public Rating[] Ratings { get; init; } = []; + public Location[] Offices { get; init; } = []; +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Models/Solicitor.cs b/backend/libs/Solicitors.Core/Models/Solicitor.cs new file mode 100644 index 0000000..01d932a --- /dev/null +++ b/backend/libs/Solicitors.Core/Models/Solicitor.cs @@ -0,0 +1,19 @@ +namespace Solicitors.Core.Models; + +public record Solicitor +{ + public Guid SolicitorId { get; set; } + + public required string Name { get; set; } + public required string RelativeUrl { get; set; } + + public string? Phone { get; set; } + public string? Email { get; set; } + public string? Website { get; set; } + public string? ShortDescription { get; set; } + public DateTime LastModified { get; set; } + + public List Cities { get; } = []; + public List Ratings { get; } = []; + public List Locations { get; } = []; +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Models/SolicitorInfo.cs b/backend/libs/Solicitors.Core/Models/SolicitorInfo.cs new file mode 100644 index 0000000..3691859 --- /dev/null +++ b/backend/libs/Solicitors.Core/Models/SolicitorInfo.cs @@ -0,0 +1,40 @@ +using Solicitors.Core.Models.View; + +namespace Solicitors.Core.Models; + +public record SolicitorInfo +{ + internal SolicitorInfo(Solicitor solicitor) + { + Id = solicitor.SolicitorId; + Name = solicitor.Name; + ShortDescription = solicitor.ShortDescription; + Phone = solicitor.Phone; + Email = solicitor.Email; + Website = solicitor.Website; + + Cities = solicitor.Cities + .Select(city => city.Name) + .ToArray(); + + Ratings = solicitor.Ratings + .Select(rating => new RatingInfo(rating)) + .ToArray(); + + Locations = solicitor.Locations + .Select(location => new LocationInfo(location)) + .ToArray(); + } + + public string Name { get; set; } + public string? ShortDescription { get; set; } + public Guid Id { get; set; } + + public string? Phone { get; set; } + public string? Email { get; set; } + public string? Website { get; set; } + + public string[] Cities { get; set; } + public RatingInfo[] Ratings { get; set; } + public LocationInfo[] Locations { get; set; } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Models/SolicitorLocation.cs b/backend/libs/Solicitors.Core/Models/SolicitorLocation.cs new file mode 100644 index 0000000..a9e787e --- /dev/null +++ b/backend/libs/Solicitors.Core/Models/SolicitorLocation.cs @@ -0,0 +1,22 @@ +namespace Solicitors.Core.Models; + +public record SolicitorLocation +{ + public Guid SolicitorLocationId { get; set; } + + public required string Address { get; set; } + public required string Phone { get; set; } + public DateTime LastModified { get; set; } + + public List LocationRatings { get; } = []; + + public Guid SolicitorId { get; set; } + public required Solicitor Solicitor { get; set; } +} + +public interface IRating +{ + decimal Value { get; } + decimal Maximum { get; } + string Provider { get; } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Models/SolicitorLocationRating.cs b/backend/libs/Solicitors.Core/Models/SolicitorLocationRating.cs new file mode 100644 index 0000000..671035a --- /dev/null +++ b/backend/libs/Solicitors.Core/Models/SolicitorLocationRating.cs @@ -0,0 +1,14 @@ +namespace Solicitors.Core.Models; + +public record SolicitorLocationRating : IRating +{ + public Guid SolicitorLocationRatingId { get; set; } + + public required decimal Value { get; set; } + public required decimal Maximum { get; set; } + public required string Provider { get; set; } + public DateTime LastModified { get; set; } + + public Guid SolicitorLocationId { get; set; } + public required SolicitorLocation SolicitorLocation { get; set; } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Models/SolicitorRating.cs b/backend/libs/Solicitors.Core/Models/SolicitorRating.cs new file mode 100644 index 0000000..5c9a702 --- /dev/null +++ b/backend/libs/Solicitors.Core/Models/SolicitorRating.cs @@ -0,0 +1,14 @@ +namespace Solicitors.Core.Models; + +public record SolicitorRating : IRating +{ + public Guid SolicitorRatingId { get; set; } + + public required decimal Value { get; set; } + public required decimal Maximum { get; set; } + public required string Provider { get; set; } + public DateTime LastModified { get; set; } + + public Guid SolicitorId { get; set; } + public required Solicitor Solicitor { get; set; } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Models/View/LocationInfo.cs b/backend/libs/Solicitors.Core/Models/View/LocationInfo.cs new file mode 100644 index 0000000..41e3d2a --- /dev/null +++ b/backend/libs/Solicitors.Core/Models/View/LocationInfo.cs @@ -0,0 +1,18 @@ +namespace Solicitors.Core.Models.View; + +public record LocationInfo +{ + public LocationInfo(SolicitorLocation location) + { + Address = location.Address; + Phone = location.Phone; + Ratings = location.LocationRatings + .Select(rating => new RatingInfo(rating)) + .ToArray(); + } + + public string Address { get; } + public string Phone { get; } + + public RatingInfo[] Ratings { get; } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Models/View/RatingInfo.cs b/backend/libs/Solicitors.Core/Models/View/RatingInfo.cs new file mode 100644 index 0000000..aed300f --- /dev/null +++ b/backend/libs/Solicitors.Core/Models/View/RatingInfo.cs @@ -0,0 +1,15 @@ +namespace Solicitors.Core.Models.View; + +public record RatingInfo +{ + public RatingInfo(IRating rating) + { + Value = rating.Value; + Maximum = rating.Maximum; + Provider = rating.Provider; + } + + public decimal Value { get; } + public decimal Maximum { get; } + public string Provider { get; } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Models/View/SolicitorSummary.cs b/backend/libs/Solicitors.Core/Models/View/SolicitorSummary.cs new file mode 100644 index 0000000..d2ce52d --- /dev/null +++ b/backend/libs/Solicitors.Core/Models/View/SolicitorSummary.cs @@ -0,0 +1,19 @@ +namespace Solicitors.Core.Models.View; + +public record SolicitorSummary +{ + internal SolicitorSummary(Solicitor solicitor, string ratingsProvider) + { + Id = solicitor.SolicitorId; + Name = solicitor.Name; + ShortDescription = solicitor.ShortDescription; + var rating = solicitor.Ratings.FirstOrDefault(x => x.Provider == ratingsProvider); + if (rating is not null) + Rating = new RatingInfo(rating); + } + + public Guid Id { get; } + public string Name { get; } + public string? ShortDescription { get; } + public RatingInfo? Rating { get; } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Ordering/DefaultSolicitorDataOrdering.cs b/backend/libs/Solicitors.Core/Ordering/DefaultSolicitorDataOrdering.cs new file mode 100644 index 0000000..59f4b28 --- /dev/null +++ b/backend/libs/Solicitors.Core/Ordering/DefaultSolicitorDataOrdering.cs @@ -0,0 +1,23 @@ +using Solicitors.Core.Models; + +namespace Solicitors.Core.Ordering; + +internal class DefaultSolicitorDataOrdering : IComparer +{ + public static IComparer Instance { get; } = new DefaultSolicitorDataOrdering(); + + private DefaultSolicitorDataOrdering() + { + } + + public int Compare(Solicitor? x, Solicitor? y) + { + if (x is null) + return y is null ? 0 : int.MaxValue; + + else if (y is null) + return int.MinValue; + + return Comparer.Default.Compare(x.SolicitorId, y.SolicitorId); + } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Ordering/ISolicitorComparerFactory.cs b/backend/libs/Solicitors.Core/Ordering/ISolicitorComparerFactory.cs new file mode 100644 index 0000000..69507b4 --- /dev/null +++ b/backend/libs/Solicitors.Core/Ordering/ISolicitorComparerFactory.cs @@ -0,0 +1,10 @@ +using Solicitors.Core.Models; + +namespace Solicitors.Core.Ordering; + +public interface ISolicitorComparerFactory +{ + IComparer GetComparer( + OrderingType? orderingType, + string ratingsProvider); +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Ordering/NameComparer.cs b/backend/libs/Solicitors.Core/Ordering/NameComparer.cs new file mode 100644 index 0000000..ddd9487 --- /dev/null +++ b/backend/libs/Solicitors.Core/Ordering/NameComparer.cs @@ -0,0 +1,26 @@ +using Solicitors.Core.Models; + +namespace Solicitors.Core.Ordering; + +internal class NameComparer(bool ascending) : IComparer +{ + public int Compare(Solicitor? x, Solicitor? y) + { + int compare; + if (ReferenceEquals(x, y)) + compare = 0; + else if (y is null) + compare = 1; + else if (x is null) + compare = -1; + else + { + compare = string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase); + + if (!ascending) + compare *= -1; + } + + return compare; + } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Ordering/OrderingType.cs b/backend/libs/Solicitors.Core/Ordering/OrderingType.cs new file mode 100644 index 0000000..1a3d38f --- /dev/null +++ b/backend/libs/Solicitors.Core/Ordering/OrderingType.cs @@ -0,0 +1,9 @@ +namespace Solicitors.Core.Ordering; + +public enum OrderingType +{ + RatingDescending, + RatingAscending, + AlphabetAscending, + AlphabetDescending +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Ordering/RatingComparer.cs b/backend/libs/Solicitors.Core/Ordering/RatingComparer.cs new file mode 100644 index 0000000..7714bd7 --- /dev/null +++ b/backend/libs/Solicitors.Core/Ordering/RatingComparer.cs @@ -0,0 +1,32 @@ +using Solicitors.Core.Models; + +namespace Solicitors.Core.Ordering; + +internal class RatingComparer(string ratingsProvider, bool ascending) : IComparer +{ + public int Compare(Solicitor? x, Solicitor? y) + { + var xRating = x?.Ratings.FirstOrDefault(r => r.Provider == ratingsProvider); + var yRating = y?.Ratings.FirstOrDefault(r => r.Provider == ratingsProvider); + + decimal compare; + + if (xRating is null) + compare = yRating is null ? 0 : 1; + else if (yRating is null) + compare = -1; + else + { + compare = (xRating.Value / xRating.Maximum) - (yRating.Value / yRating.Maximum); + + if (!ascending) + compare *= -1; + } + + if (compare >= 0) + return (int)Math.Ceiling(compare); + else + return (int)Math.Floor(compare); + + } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Ordering/SolicitorComparerFactory.cs b/backend/libs/Solicitors.Core/Ordering/SolicitorComparerFactory.cs new file mode 100644 index 0000000..784ec2f --- /dev/null +++ b/backend/libs/Solicitors.Core/Ordering/SolicitorComparerFactory.cs @@ -0,0 +1,20 @@ +using Solicitors.Core.Models; + +namespace Solicitors.Core.Ordering; + +internal class SolicitorComparerFactory : ISolicitorComparerFactory +{ + public IComparer GetComparer( + OrderingType? orderingType, + string ratingsProvider) + { + return orderingType switch + { + OrderingType.RatingAscending => new RatingComparer(ratingsProvider, true), + OrderingType.RatingDescending => new RatingComparer(ratingsProvider, false), + OrderingType.AlphabetAscending => new NameComparer(true), + OrderingType.AlphabetDescending => new NameComparer(false), + _ => DefaultSolicitorDataOrdering.Instance + }; + } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/PaginationResponse.cs b/backend/libs/Solicitors.Core/PaginationResponse.cs new file mode 100644 index 0000000..bd0694f --- /dev/null +++ b/backend/libs/Solicitors.Core/PaginationResponse.cs @@ -0,0 +1,7 @@ +namespace Solicitors.Core; + +public record PaginationResponse(int Total, T[] Data) +{ + public T[] Data { get; set; } = Data; + public int Total { get; set; } = Total; +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/RatingsProviderService.cs b/backend/libs/Solicitors.Core/RatingsProviderService.cs new file mode 100644 index 0000000..7fe71a5 --- /dev/null +++ b/backend/libs/Solicitors.Core/RatingsProviderService.cs @@ -0,0 +1,11 @@ +using Solicitors.Core.Data; + +namespace Solicitors.Core; + +internal class RatingsProviderService(IRatingsProviderRepository repo) : IRatingsProviderService +{ + public async Task GetRatingsProvidersAsync(CancellationToken cancellationToken) + { + return await repo.GetAllRatingsProvidersAsync().ToArrayAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/ReadOnlyCitiesService.cs b/backend/libs/Solicitors.Core/ReadOnlyCitiesService.cs new file mode 100644 index 0000000..19f7e61 --- /dev/null +++ b/backend/libs/Solicitors.Core/ReadOnlyCitiesService.cs @@ -0,0 +1,12 @@ +using Solicitors.Core.Data; + +namespace Solicitors.Core; + +internal class ReadOnlyCitiesService(IReadOnlyCitiesRepository repo) : IReadOnlyCitiesService +{ + public async Task GetAllCitiesAsync(CancellationToken cancellationToken) + { + var cities = repo.GetCitiesAsync(); + return await cities.ToArrayAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/SolicitorService.cs b/backend/libs/Solicitors.Core/SolicitorService.cs new file mode 100644 index 0000000..114ea43 --- /dev/null +++ b/backend/libs/Solicitors.Core/SolicitorService.cs @@ -0,0 +1,44 @@ +using Solicitors.Core.Data; +using Solicitors.Core.Misc; +using Solicitors.Core.Models; +using Solicitors.Core.Models.View; +using Solicitors.Core.Ordering; + +namespace Solicitors.Core; + +internal class SolicitorService(IReadOnlySolicitorRepository repo) : ISolicitorService +{ + public async Task> GetSolicitorSummariesAsync( + Pagination pagination, + string ratingsProvider, + IFilter? filter = null, + IComparer? ordering = null, + CancellationToken cancellationToken = default) + { + var allSolicitors = repo.GetAllSolicitorsAsync(); + if (filter is not null) + allSolicitors = allSolicitors.Where(filter.Filter); + + var matchingSolicitors = await allSolicitors.ToArrayAsync(cancellationToken); + + ordering ??= DefaultSolicitorDataOrdering.Instance; + + var results = matchingSolicitors + .OrderBy(x => x, ordering) + .Skip((int)((pagination.CurrentPage - 1) * pagination.PageSize)) + .Take((int)pagination.PageSize) + .Select(x => new SolicitorSummary(x, ratingsProvider)) + .ToArray(); + + return new PaginationResponse(matchingSolicitors.Length, results); + } + + public async Task GetSolicitorInfoByIdAsync(Guid id, CancellationToken cancellationToken = default) + { + var data = await repo.GetSolicitorByIdAsync(id, cancellationToken); + if (data is null) + return null; + + return new SolicitorInfo(data); + } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Core/Solicitors.Core.csproj b/backend/libs/Solicitors.Core/Solicitors.Core.csproj new file mode 100644 index 0000000..777a709 --- /dev/null +++ b/backend/libs/Solicitors.Core/Solicitors.Core.csproj @@ -0,0 +1,14 @@ + + + + net10.0 + enable + enable + + + + + + + + diff --git a/backend/libs/Solicitors.Data/DIExtensions.cs b/backend/libs/Solicitors.Data/DIExtensions.cs new file mode 100644 index 0000000..0c0087a --- /dev/null +++ b/backend/libs/Solicitors.Data/DIExtensions.cs @@ -0,0 +1,48 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Solicitors.Core.Data; +using Solicitors.Data.RepositorySetup; +using Solicitors.Data.RepositorySetup.InMemory; +using Solicitors.Data.RepositorySetup.Sqlite; + +namespace Solicitors.Data; + +[SuppressMessage("ReSharper", "InconsistentNaming")] +public static class DIExtensions +{ + public static IServiceCollection AddData(this IServiceCollection services, IConfiguration configuration) + { + return services + .AddScoped() + .AddScoped(sp => sp.GetRequiredService()) + .AddScoped(sp => sp.GetRequiredService()) + .AddScoped(sp => sp.GetRequiredService()) + .AddScoped(sp => sp.GetRequiredService()) + .AddSingleton(GetSetupService(configuration)); + } + + private static IRepoSetupService GetSetupService(IConfiguration configuration) + { + var options = ReadConfigForOptions(configuration); + + return options switch + { + InMemoryDbSetupOptions inMemOptions => new InMemorySetupService(inMemOptions), + SqliteDbSetupOptions sqliteOptions => new SqliteSetupService(sqliteOptions), + _ => throw new ArgumentException("Options type not supported", nameof(configuration)) + }; + } + + private static IRepoSetupOptions? ReadConfigForOptions(IConfiguration configuration) + { + var dbConfig = configuration.GetSection("Database"); + var dbType = dbConfig["Type"]?.ToLower(); + return dbType switch + { + "inmemory" => dbConfig.Get(), + "sqlite" => dbConfig.Get(), + _ => null + }; + } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Data/RepositorySetup/IRepoSetupOptions.cs b/backend/libs/Solicitors.Data/RepositorySetup/IRepoSetupOptions.cs new file mode 100644 index 0000000..0077099 --- /dev/null +++ b/backend/libs/Solicitors.Data/RepositorySetup/IRepoSetupOptions.cs @@ -0,0 +1,3 @@ +namespace Solicitors.Data.RepositorySetup; + +public interface IRepoSetupOptions; \ No newline at end of file diff --git a/backend/libs/Solicitors.Data/RepositorySetup/IRepoSetupService.cs b/backend/libs/Solicitors.Data/RepositorySetup/IRepoSetupService.cs new file mode 100644 index 0000000..7e13c54 --- /dev/null +++ b/backend/libs/Solicitors.Data/RepositorySetup/IRepoSetupService.cs @@ -0,0 +1,8 @@ +using Microsoft.EntityFrameworkCore; + +namespace Solicitors.Data.RepositorySetup; + +internal interface IRepoSetupService +{ + void OnConfiguring(DbContextOptionsBuilder optionsBuilder); +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Data/RepositorySetup/InMemory/InMemoryDbSetupOptions.cs b/backend/libs/Solicitors.Data/RepositorySetup/InMemory/InMemoryDbSetupOptions.cs new file mode 100644 index 0000000..3387933 --- /dev/null +++ b/backend/libs/Solicitors.Data/RepositorySetup/InMemory/InMemoryDbSetupOptions.cs @@ -0,0 +1,6 @@ +namespace Solicitors.Data.RepositorySetup.InMemory; + +public class InMemoryDbSetupOptions(string dbName) : IRepoSetupOptions +{ + public string DbName { get; } = dbName; +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Data/RepositorySetup/InMemory/InMemorySetupService.cs b/backend/libs/Solicitors.Data/RepositorySetup/InMemory/InMemorySetupService.cs new file mode 100644 index 0000000..3295f2b --- /dev/null +++ b/backend/libs/Solicitors.Data/RepositorySetup/InMemory/InMemorySetupService.cs @@ -0,0 +1,11 @@ +using Microsoft.EntityFrameworkCore; + +namespace Solicitors.Data.RepositorySetup.InMemory; + +internal class InMemorySetupService(InMemoryDbSetupOptions config) : IRepoSetupService +{ + public void OnConfiguring(DbContextOptionsBuilder options) + { + options.UseInMemoryDatabase(config.DbName); + } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Data/RepositorySetup/Sqlite/SqliteDbSetupOptions.cs b/backend/libs/Solicitors.Data/RepositorySetup/Sqlite/SqliteDbSetupOptions.cs new file mode 100644 index 0000000..205410c --- /dev/null +++ b/backend/libs/Solicitors.Data/RepositorySetup/Sqlite/SqliteDbSetupOptions.cs @@ -0,0 +1,6 @@ +namespace Solicitors.Data.RepositorySetup.Sqlite; + +public class SqliteDbSetupOptions(string dbPath) : IRepoSetupOptions +{ + public string DbPath { get; } = dbPath; +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Data/RepositorySetup/Sqlite/SqliteSetupService.cs b/backend/libs/Solicitors.Data/RepositorySetup/Sqlite/SqliteSetupService.cs new file mode 100644 index 0000000..d2b6a76 --- /dev/null +++ b/backend/libs/Solicitors.Data/RepositorySetup/Sqlite/SqliteSetupService.cs @@ -0,0 +1,11 @@ +using Microsoft.EntityFrameworkCore; + +namespace Solicitors.Data.RepositorySetup.Sqlite; + +internal class SqliteSetupService(SqliteDbSetupOptions config) : IRepoSetupService +{ + public void OnConfiguring(DbContextOptionsBuilder options) + { + options.UseSqlite($"Data Source={config.DbPath}"); + } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.Data/Solicitors.Data.csproj b/backend/libs/Solicitors.Data/Solicitors.Data.csproj new file mode 100644 index 0000000..cec2d80 --- /dev/null +++ b/backend/libs/Solicitors.Data/Solicitors.Data.csproj @@ -0,0 +1,21 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + + + + + diff --git a/backend/libs/Solicitors.Data/SolicitorsRepository.cs b/backend/libs/Solicitors.Data/SolicitorsRepository.cs new file mode 100644 index 0000000..6638591 --- /dev/null +++ b/backend/libs/Solicitors.Data/SolicitorsRepository.cs @@ -0,0 +1,335 @@ +using Microsoft.EntityFrameworkCore; +using Solicitors.Core.Data; +using Solicitors.Core.Models; +using Solicitors.Core.Models.Imports; +using Solicitors.Data.RepositorySetup; + +namespace Solicitors.Data; + +internal class SolicitorsRepository(IRepoSetupService setup) + : DbContext, ISolicitorRepository, IReadOnlyCitiesRepository, IRatingsProviderRepository +{ + public DbSet Solicitors { get; set; } + public DbSet SolicitorRatings { get; set; } + public DbSet SolicitorLocations { get; set; } + public DbSet SolicitorLocationRatings { get; set; } + public DbSet Cities { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => setup.OnConfiguring(optionsBuilder); + + public Task EnsureCreatedAsync(CancellationToken cancellationToken) + => Database.EnsureCreatedAsync(cancellationToken); + + public IAsyncEnumerable GetAllSolicitorsAsync() + => Solicitors + .Include(solicitor => solicitor.Cities) + .Include(solicitor => solicitor.Locations) + .ThenInclude(location => location.LocationRatings) + .Include(solicitor => solicitor.Ratings) + .AsAsyncEnumerable(); + + public Task GetSolicitorByIdAsync(Guid id, CancellationToken cancellationToken = default) + => Solicitors + .Include(solicitor => solicitor.Cities) + .Include(solicitor => solicitor.Locations) + .ThenInclude(location => location.LocationRatings) + .Include(solicitor => solicitor.Ratings) + .FirstOrDefaultAsync(x => x.SolicitorId == id, cancellationToken); + + public IAsyncEnumerable GetCitiesAsync() + { + return Cities + .Select(city => city.Name) + .AsAsyncEnumerable(); + } + + public IAsyncEnumerable GetAllRatingsProvidersAsync() + { + return SolicitorRatings + .Select(rating => rating.Provider) + .Distinct() + .OrderBy(x => x == "Solicitors.com" ? 0 : 1) + .ThenBy(x => x) + .AsAsyncEnumerable(); + } + + public async Task AddOrUpdateSolicitorAsync(SolicitorData solicitor, CancellationToken cancellationToken) + { + var matchingSolicitor = await Solicitors + .Include(existing => existing.Cities) + .Include(existing => existing.Locations) + .ThenInclude(location => location.LocationRatings) + .Include(existing => existing.Ratings) + .FirstOrDefaultAsync( + item => item.Name == solicitor.Name, + cancellationToken: cancellationToken); + + if (matchingSolicitor is null) + await AddNewSolicitorAsync(solicitor, cancellationToken); + else + await UpdateExistingSolicitorAsync(solicitor, matchingSolicitor, cancellationToken); + + await SaveChangesAsync(cancellationToken); + } + + public Task RemoveStaleEntriesAsync(TimeSpan staleAge, CancellationToken cancellationToken) + { + var staleTime = DateTime.UtcNow - staleAge; + + Solicitors.RemoveRange(Solicitors.Where(x => x.LastModified < staleTime)); + SolicitorLocations.RemoveRange(SolicitorLocations.Where(x => x.LastModified < staleTime)); + SolicitorRatings.RemoveRange(SolicitorRatings.Where(x => x.LastModified < staleTime)); + SolicitorLocationRatings.RemoveRange(SolicitorLocationRatings.Where(x => x.LastModified < staleTime)); + Cities.RemoveRange(Cities.Where(x => x.LastModified < staleTime)); + + return SaveChangesAsync(cancellationToken); + } + + private async Task AddNewSolicitorAsync( + SolicitorData solicitor, + CancellationToken cancellationToken) + { + var newSol = new Solicitor + { + Name = solicitor.Name, + RelativeUrl = solicitor.UrlPath + }; + UpdateSolicitorRootData(newSol, solicitor); + Solicitors.Add(newSol); + + var cityTasks = solicitor.Cities + .Distinct() + .Select(async city => + { + var matchingCity = await GetCityByNameAsync(city, cancellationToken); + + if (matchingCity is null) + AddNewCityToSolicitor(city, newSol); + else + UpdateExistingCity(matchingCity, newSol); + }); + + await Task.WhenAll(cityTasks); + + foreach (var rating in solicitor.Ratings) + AddNewRatingToSolicitor(rating, newSol); + + foreach (var location in solicitor.Offices) + AddNewLocationToSolicitor(location, newSol); + } + + private void UpdateSolicitorRootData(Solicitor solicitor, SolicitorData data) + { + solicitor.Name = data.Name; + solicitor.RelativeUrl = data.UrlPath; + solicitor.Phone = data.Phone; + solicitor.Email = data.Email; + solicitor.Website = data.Website; + solicitor.ShortDescription = data.ShortDescription; + solicitor.LastModified = DateTime.UtcNow; + } + + private void AddNewCityToSolicitor(string cityName, Solicitor solicitor) + { + var newCity = new City + { + Name = cityName + }; + Cities.Add(newCity); + UpdateExistingCity(newCity, solicitor); + } + + private void AddNewRatingToSolicitor(Rating rating, Solicitor solicitor) + { + var newRating = new SolicitorRating + { + Value = rating.Value, + Maximum = rating.MaxValue, + Provider = rating.RatingProvider, + Solicitor = solicitor, + LastModified = DateTime.UtcNow + }; + SolicitorRatings.Add(newRating); + solicitor.Ratings.Add(newRating); + } + + private void AddNewLocationToSolicitor(Location location, Solicitor solicitor) + { + var newLocation = new SolicitorLocation + { + Address = location.Address, + Phone = location.Phone, + Solicitor = solicitor, + LastModified = DateTime.UtcNow + }; + SolicitorLocations.Add(newLocation); + solicitor.Locations.Add(newLocation); + + foreach (var rating in location.LocationRatings) + AddNewRatingToLocation(rating, newLocation); + } + + private void AddNewRatingToLocation(Rating rating, SolicitorLocation location) + { + var newLocRating = new SolicitorLocationRating + { + Value = rating.Value, + Maximum = rating.MaxValue, + Provider = rating.RatingProvider, + SolicitorLocation = location, + LastModified = DateTime.UtcNow + }; + SolicitorLocationRatings.Add(newLocRating); + location.LocationRatings.Add(newLocRating); + } + + private Task UpdateExistingSolicitorAsync( + SolicitorData solicitor, + Solicitor matchingSolicitor, + CancellationToken cancellationToken) + { + UpdateSolicitorRootData(matchingSolicitor, solicitor); + var citiesUpdate = UpdateExistingCitiesAsync(solicitor, matchingSolicitor, cancellationToken); + UpdateExistingRatings(solicitor, matchingSolicitor); + UpdateExistingLocations(solicitor, matchingSolicitor); + return citiesUpdate; + } + + private async Task UpdateExistingCitiesAsync( + SolicitorData solicitor, + Solicitor matchingSolicitor, + CancellationToken cancellationToken) + { + var lostCities = matchingSolicitor.Cities + .Where(city => solicitor.Cities.All(cityName => cityName != city.Name)) + .ToArray(); + + foreach (var city in lostCities) + { + matchingSolicitor.Cities.Remove(city); + city.Solicitors.Remove(matchingSolicitor); + } + foreach (var cityName in solicitor.Cities.Distinct()) + { + var matchingCity = matchingSolicitor.Cities + .FirstOrDefault(city => cityName == city.Name); + + matchingCity ??= await GetCityByNameAsync(cityName, cancellationToken); + + if (matchingCity is null) + AddNewCityToSolicitor(cityName, matchingSolicitor); + else + UpdateExistingCity(matchingCity, matchingSolicitor); + } + } + + private void UpdateExistingRatings( + SolicitorData solicitor, + Solicitor matchingSolicitor) + { + var lostRatings = matchingSolicitor.Ratings + .Where(solicitorRating => solicitor.Ratings.All(rating => rating.RatingProvider != solicitorRating.Provider)) + .ToArray(); + + foreach (var rating in lostRatings) + { + matchingSolicitor.Ratings.Remove(rating); + SolicitorRatings.Remove(rating); + } + + foreach (var rating in solicitor.Ratings) + { + var matchingRating = matchingSolicitor.Ratings + .FirstOrDefault(solicitorRating => rating.RatingProvider == solicitorRating.Provider); + + if (matchingRating is null) + AddNewRatingToSolicitor(rating, matchingSolicitor); + else + UpdateExistingRating(rating, matchingRating); + } + } + + private void UpdateExistingRating(Rating rating, SolicitorRating existingRating) + { + existingRating.Value = rating.Value; + existingRating.Maximum = rating.MaxValue; + existingRating.LastModified = DateTime.UtcNow; + } + + private void UpdateExistingLocations( + SolicitorData solicitor, + Solicitor matchingSolicitor) + { + var lostLocations = matchingSolicitor.Locations + .Where(location => solicitor.Offices.All(office => office.Address != location.Address)) + .ToArray(); + + foreach (var location in lostLocations) + { + matchingSolicitor.Locations.Remove(location); + SolicitorLocations.Remove(location); + } + + foreach (var office in solicitor.Offices) + { + var location = matchingSolicitor.Locations + .FirstOrDefault(location => office.Address == location.Address); + + if (location is null) + AddNewLocationToSolicitor(office, matchingSolicitor); + else + UpdateExistingLocation(office, location); + } + } + + private void UpdateExistingLocation(Location location, SolicitorLocation existingLocation) + { + existingLocation.Phone = location.Phone; + existingLocation.LastModified = DateTime.UtcNow; + + var lostRatings = existingLocation.LocationRatings + .Where(locationRating => location.LocationRatings.All(rating => locationRating.Provider != rating.RatingProvider)) + .ToArray(); + + foreach (var rating in lostRatings) + { + existingLocation.LocationRatings.Remove(rating); + SolicitorLocationRatings.Remove(rating); + } + + foreach (var rating in location.LocationRatings) + { + var existingRating = existingLocation.LocationRatings + .FirstOrDefault(locationRating => locationRating.Provider == rating.RatingProvider); + + if (existingRating is null) + AddNewRatingToLocation(rating, existingLocation); + else + UpdateExistingLocationRating(rating, existingRating); + } + } + + private void UpdateExistingLocationRating(Rating rating, SolicitorLocationRating existingRating) + { + existingRating.Value = rating.Value; + existingRating.Maximum = rating.MaxValue; + existingRating.LastModified = DateTime.UtcNow; + } + + private void UpdateExistingCity(City city, Solicitor solicitor) + { + city.Solicitors.Add(solicitor); + solicitor.Cities.Add(city); + city.LastModified = DateTime.UtcNow; + } + + private Task GetCityByNameAsync(string cityName, CancellationToken cancellationToken) + { + return Cities + .Include(city => city.Solicitors) + .FirstOrDefaultAsync( + city => city.Name == cityName, + cancellationToken: cancellationToken); + } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.HtmlParsing/DIExtensions.cs b/backend/libs/Solicitors.HtmlParsing/DIExtensions.cs new file mode 100644 index 0000000..b12689a --- /dev/null +++ b/backend/libs/Solicitors.HtmlParsing/DIExtensions.cs @@ -0,0 +1,13 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.DependencyInjection; + +namespace Solicitors.HtmlParsing; + +[SuppressMessage("ReSharper", "InconsistentNaming")] +public static class DIExtensions +{ + public static IServiceCollection AddHtmlParsing(this IServiceCollection services) + { + return services.AddSingleton(); + } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.HtmlParsing/HtmlParser.cs b/backend/libs/Solicitors.HtmlParsing/HtmlParser.cs new file mode 100644 index 0000000..7b45a5b --- /dev/null +++ b/backend/libs/Solicitors.HtmlParsing/HtmlParser.cs @@ -0,0 +1,130 @@ +namespace Solicitors.HtmlParsing; + +using Models; + +internal class HtmlParser : IHtmlParser +{ + private readonly string[] _voidElements = + [ + "area", + "base", + "br", + "col", + "embed", + "hr", + "img", + "input", + "link", + "meta", + "source", + "track", + "wbr" + ]; + + public IEnumerable ParseHtml(string html) + { + // wanted to do this in one pass but was quite time-consuming + // so taking a little lesson from compiler development + // and lex the text into tokens before parsing :) + // deferred execution might help it still be "one pass"? + var stringEnumerator = html.GetEnumerator(); + var tokens = LexHtml(stringEnumerator); + var tokenEnumerator = tokens.GetEnumerator(); + return ParseHtml(tokenEnumerator); + } + + private IEnumerable LexHtml(CharEnumerator content) + { + var work = ""; + while (content.MoveNext()) + { + if (content.Current == '<' && !string.IsNullOrWhiteSpace(work)) + { + yield return work.Trim(); + work = ""; + } + + work += content.Current; + + if (content.Current == '>' && !string.IsNullOrWhiteSpace(work)) + { + yield return work.Trim(); + work = ""; + } + } + } + + private IEnumerable ParseHtml(IEnumerator tokens) + { + while (tokens.MoveNext()) + { + var token = tokens.Current; + if (!token.StartsWith('<')) + { + yield return new StringNode(token); + continue; + } + + var noBraces = token.Substring(1, token.Length - 2); + var split = noBraces.Split(' '); + IHtmlAttribute[] attributes = []; + if (split.Length > 1) + { + attributes = ParseAttributes(noBraces).ToArray(); + } + + if (IsVoidOrSelfClosingElement(token)) + yield return new HtmlNode(split.First(), attributes); + else if (token.StartsWith("")) + return true; + + var tagName = token.Split(' ').First()[1..]; + if (tagName.EndsWith('>')) + tagName = tagName[..^1]; + return _voidElements.Contains(tagName); + } + + private IEnumerable ParseAttributes(string tokenNoBraces) + { + var attributesString = string.Join(' ', tokenNoBraces.Split(' ').Skip(1)); + var attString = ""; + var inQuote = false; + foreach (var c in attributesString) + { + if (!inQuote && c == ' ') + { + yield return ParseAttribute(attString); + attString = ""; + } + else + { + attString += c; + if (c == '\"') + inQuote = !inQuote; + } + } + + yield return ParseAttribute(attString); + } + + private IHtmlAttribute ParseAttribute(string attString) + { + var split = attString.Split('='); + if (split.Length > 1) + return new HtmlValueAttribute(split[0], string.Join('=', split.Skip(1))); + else + return new HtmlAttribute(attString); + } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.HtmlParsing/IHtmlParser.cs b/backend/libs/Solicitors.HtmlParsing/IHtmlParser.cs new file mode 100644 index 0000000..199a93c --- /dev/null +++ b/backend/libs/Solicitors.HtmlParsing/IHtmlParser.cs @@ -0,0 +1,8 @@ +namespace Solicitors.HtmlParsing; + +using Models; + +public interface IHtmlParser +{ + IEnumerable ParseHtml(string html); +} \ No newline at end of file diff --git a/backend/libs/Solicitors.HtmlParsing/Models/HtmlAttribute.cs b/backend/libs/Solicitors.HtmlParsing/Models/HtmlAttribute.cs new file mode 100644 index 0000000..70a8fea --- /dev/null +++ b/backend/libs/Solicitors.HtmlParsing/Models/HtmlAttribute.cs @@ -0,0 +1,6 @@ +namespace Solicitors.HtmlParsing.Models; + +internal class HtmlAttribute(string name) : IHtmlAttribute +{ + public string Name { get; } = name; +} \ No newline at end of file diff --git a/backend/libs/Solicitors.HtmlParsing/Models/HtmlNode.cs b/backend/libs/Solicitors.HtmlParsing/Models/HtmlNode.cs new file mode 100644 index 0000000..971014f --- /dev/null +++ b/backend/libs/Solicitors.HtmlParsing/Models/HtmlNode.cs @@ -0,0 +1,92 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Solicitors.HtmlParsing.Models; + +internal class HtmlNode(string name, IHtmlAttribute[] attributes) + : IHtmlNode +{ + public virtual bool TryGetByTagName( + string tagName, + [NotNullWhen(true)] out IHtmlNode? node, + bool noChildren = false) + { + node = null; + if (tagName == name) + node = this; + + return node is not null; + } + + private string[] Classes + { + get + { + var classAttribute = attributes + .Where(a => a is HtmlValueAttribute) + .Select(a => (a as HtmlValueAttribute)!) + .FirstOrDefault(a => a.Name.Equals("class", StringComparison.CurrentCultureIgnoreCase)); + + return classAttribute is not null + ? classAttribute.Value.Substring(1, classAttribute.Value.Length - 2).Split(' ') + : []; + } + } + + public virtual bool TryGetByClass( + string className, + [NotNullWhen(true)] out IHtmlNode? node, + bool noChildren = false) + { + node = null; + + if (Classes.Contains(className, StringComparer.InvariantCultureIgnoreCase)) + node = this; + + return node is not null; + } + + public virtual bool TryGetByTagNameAndClass( + string tagName, + string className, + [NotNullWhen(true)] out IHtmlNode? node, + bool noChildren = false) + { + node = null; + if (tagName == name && Classes.Contains(className, StringComparer.InvariantCultureIgnoreCase)) + node = this; + + return node is not null; + } + + public virtual bool TryGetText([NotNullWhen(true)] out string? text) + { + text = null; + return false; + } + + public virtual bool TryGetChildren([NotNullWhen(true)] out IEnumerable? children) + { + children = null; + return false; + } + + public bool HasAttribute(string attributeName) + { + return attributes + .Any(a => a.Name.Equals(attributeName, StringComparison.InvariantCultureIgnoreCase)); + } + + public bool TryGetAttributeValue(string attributeName, [NotNullWhen(true)] out string? attributeValue) + { + attributeValue = null; + + var matchingAttribute = attributes.FirstOrDefault( + a => a.Name.Equals(attributeName, StringComparison.InvariantCultureIgnoreCase) + ); + + if (matchingAttribute is HtmlValueAttribute valueAttribute) + attributeValue = valueAttribute.Value; + + return attributeValue is not null; + } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.HtmlParsing/Models/HtmlValueAttribute.cs b/backend/libs/Solicitors.HtmlParsing/Models/HtmlValueAttribute.cs new file mode 100644 index 0000000..c8ea5e4 --- /dev/null +++ b/backend/libs/Solicitors.HtmlParsing/Models/HtmlValueAttribute.cs @@ -0,0 +1,6 @@ +namespace Solicitors.HtmlParsing.Models; + +internal class HtmlValueAttribute(string name, string value) : HtmlAttribute(name) +{ + public string Value { get; } = value; +} \ No newline at end of file diff --git a/backend/libs/Solicitors.HtmlParsing/Models/IHtmlAttribute.cs b/backend/libs/Solicitors.HtmlParsing/Models/IHtmlAttribute.cs new file mode 100644 index 0000000..4240a2e --- /dev/null +++ b/backend/libs/Solicitors.HtmlParsing/Models/IHtmlAttribute.cs @@ -0,0 +1,6 @@ +namespace Solicitors.HtmlParsing.Models; + +internal interface IHtmlAttribute +{ + string Name { get; } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.HtmlParsing/Models/IHtmlNode.cs b/backend/libs/Solicitors.HtmlParsing/Models/IHtmlNode.cs new file mode 100644 index 0000000..e473d8f --- /dev/null +++ b/backend/libs/Solicitors.HtmlParsing/Models/IHtmlNode.cs @@ -0,0 +1,64 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Solicitors.HtmlParsing.Models; + +public interface IHtmlNode +{ + /// + /// Searches this node and its direct children for a matching tag name. + /// + /// The tag name to match. + /// If found, the node that matches the tag name. + /// If true, only check this node, not its children. + /// True if this node, or any of its direct children, have a matching tag name; false otherwise. + bool TryGetByTagName( + string tagName, + [NotNullWhen(true)] out IHtmlNode? node, + bool noChildren = false); + + /// + /// Searches this node and its direct children for a matching class. + /// + /// The class name to match. + /// If found, the node that matches the class name. + /// If true, only check this node, not its children. + /// True if this node, or any of its direct children, have a matching class name; false otherwise. + bool TryGetByClass( + string className, + [NotNullWhen(true)] out IHtmlNode? node, + bool noChildren = false); + + /// + /// Searches this node and its direct children for a matching tag name and matching class on the same node. + /// + /// The tag name to match. + /// The class name to match. + /// If found, the node that matches the tag name and class name. + /// If true, only check this node, not its children. + /// True if this node, or any of its direct children, have a matching tag name and class name; false otherwise. + bool TryGetByTagNameAndClass( + string tagName, + string className, + [NotNullWhen(true)] out IHtmlNode? node, + bool noChildren = false); + + /// + /// Checks this node (and *not* its children) to see if it is text content. + /// + /// If found, the text content. + /// True if the node is a text node, false otherwise. + bool TryGetText([NotNullWhen(true)] out string? text); + + /// + /// Checks this node to see if it has child elements. + /// + /// If found, the child elements. + /// True if the node has child elements, false otherwise. + bool TryGetChildren([NotNullWhen(true)] out IEnumerable? children); + + bool HasAttribute(string attributeName); + + bool TryGetAttributeValue( + string attributeName, + [NotNullWhen(true)] out string? attributeValue); +} \ No newline at end of file diff --git a/backend/libs/Solicitors.HtmlParsing/Models/ParentHtmlNode.cs b/backend/libs/Solicitors.HtmlParsing/Models/ParentHtmlNode.cs new file mode 100644 index 0000000..ae229b1 --- /dev/null +++ b/backend/libs/Solicitors.HtmlParsing/Models/ParentHtmlNode.cs @@ -0,0 +1,92 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Solicitors.HtmlParsing.Models; + +internal class ParentHtmlNode(string tagName, IHtmlAttribute[] attributes, IHtmlNode[] children) + : HtmlNode(tagName, attributes) +{ + public override bool TryGetByTagName( + string tagName, + [NotNullWhen(true)] out IHtmlNode? node, + bool noChildren = false) + { + node = null; + if (!base.TryGetByTagName(tagName, out node, noChildren) && !noChildren) + { + var nodesToCheck = new Queue(children); + while (nodesToCheck.TryDequeue(out var child)) + { + if (child.TryGetByTagName(tagName, out var childNode, true)) + { + node = childNode; + break; + } + + if (child.TryGetChildren(out var grandChildren)) + foreach (var grandChild in grandChildren) + nodesToCheck.Enqueue(grandChild); + } + } + + return node is not null; + } + + public override bool TryGetByClass( + string className, + [NotNullWhen(true)] out IHtmlNode? node, + bool noChildren = false) + { + node = null; + if (!base.TryGetByClass(className, out node, noChildren) && !noChildren) + { + var nodesToCheck = new Queue(children); + while (nodesToCheck.TryDequeue(out var child)) + { + if (child.TryGetByClass(className, out var childNode, true)) + { + node = childNode; + break; + } + + if (child.TryGetChildren(out var grandChildren)) + foreach (var grandChild in grandChildren) + nodesToCheck.Enqueue(grandChild); + } + } + + return node is not null; + } + + public override bool TryGetByTagNameAndClass( + string tagName, + string className, + [NotNullWhen(true)] out IHtmlNode? node, + bool noChildren = false) + { + node = null; + if (!base.TryGetByTagNameAndClass(tagName, className, out node, noChildren) && !noChildren) + { + var nodesToCheck = new Queue(children); + while (nodesToCheck.TryDequeue(out var child)) + { + if (child.TryGetByTagNameAndClass(tagName, className, out var childNode, true)) + { + node = childNode; + break; + } + + if (child.TryGetChildren(out var grandChildren)) + foreach (var grandChild in grandChildren) + nodesToCheck.Enqueue(grandChild); + } + } + + return node is not null; + } + + public override bool TryGetChildren([NotNullWhen(true)] out IEnumerable? children1) + { + children1 = children; + return children.Length != 0; + } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.HtmlParsing/Models/StringNode.cs b/backend/libs/Solicitors.HtmlParsing/Models/StringNode.cs new file mode 100644 index 0000000..6b2009c --- /dev/null +++ b/backend/libs/Solicitors.HtmlParsing/Models/StringNode.cs @@ -0,0 +1,57 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Solicitors.HtmlParsing.Models; + +internal class StringNode(string content) + : IHtmlNode +{ + public bool TryGetByTagName( + string tagName, + [NotNullWhen(true)] out IHtmlNode? node, + bool noChildren = false) + { + node = null; + return false; + } + + public bool TryGetByClass( + string className, + [NotNullWhen(true)] out IHtmlNode? node, + bool noChildren = false) + { + node = null; + return false; + } + + public bool TryGetByTagNameAndClass( + string tagName, + string className, + [NotNullWhen(true)] out IHtmlNode? node, + bool noChildren = false) + { + node = null; + return false; + } + + public bool TryGetText([NotNullWhen(true)] out string? text) + { + text = content; + return !string.IsNullOrEmpty(text); + } + + public bool TryGetChildren([NotNullWhen(true)] out IEnumerable? children) + { + children = null; + return false; + } + + public bool HasAttribute(string attributeName) => false; + + public bool TryGetAttributeValue( + string attributeName, + [NotNullWhen(true)] out string? attributeValue) + { + attributeValue = null; + return false; + } +} \ No newline at end of file diff --git a/backend/libs/Solicitors.HtmlParsing/Solicitors.HtmlParsing.csproj b/backend/libs/Solicitors.HtmlParsing/Solicitors.HtmlParsing.csproj new file mode 100644 index 0000000..90bc4ed --- /dev/null +++ b/backend/libs/Solicitors.HtmlParsing/Solicitors.HtmlParsing.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + diff --git a/backend/tests/Solicitors.Core.Tests/SolicitorServiceTests.cs b/backend/tests/Solicitors.Core.Tests/SolicitorServiceTests.cs new file mode 100644 index 0000000..574cf5c --- /dev/null +++ b/backend/tests/Solicitors.Core.Tests/SolicitorServiceTests.cs @@ -0,0 +1,130 @@ +using Moq; +using Solicitors.Core.Data; +using Solicitors.Core.Models; + +namespace Solicitors.Core.Tests; + +public class SolicitorServiceTests +{ + private readonly Mock _mockRepo; + private readonly SolicitorService _service; + + public SolicitorServiceTests() + { + _mockRepo = new Mock(); + _service = new SolicitorService(_mockRepo.Object); + } + + [Fact] + public async Task GetSolicitorInfoByIdAsync_Should_ReturnNull_When_NoMatchingSolicitor() + { + //arrange + var cts = new CancellationTokenSource(); + var id = Guid.NewGuid(); + _mockRepo + .Setup(x => x.GetSolicitorByIdAsync(It.IsAny(), cts.Token)) + .ReturnsAsync(default(Solicitor?)); + + //act + var result = await _service.GetSolicitorInfoByIdAsync(id, cts.Token); + + //assert + Assert.Null(result); + _mockRepo.Verify(x => x.GetSolicitorByIdAsync(id, cts.Token), Times.Once); + } + + [Fact] + public async Task GetSolicitorInfoByIdAsync_Should_ReturnMainSolicitorInfo_When_MatchingSolicitor() + { + //arrange + var cts = new CancellationTokenSource(); + var id = Guid.NewGuid(); + + var expected = new Solicitor + { + SolicitorId = id, + Name = "Solicitor", + ShortDescription = "Short Desc", + Phone = "000 0000 0000", + Email = "example@example.com", + Website = "example.com", + RelativeUrl = "/-some-solicitor.html" + }; + + _mockRepo + .Setup(x => x.GetSolicitorByIdAsync(It.IsAny(), cts.Token)) + .ReturnsAsync(expected); + + //act + var result = await _service.GetSolicitorInfoByIdAsync(id, cts.Token); + + //assert + Assert.NotNull(result); + Assert.Equal(result.Id, expected.SolicitorId); + Assert.Equal(result.Name, expected.Name); + Assert.Equal(result.ShortDescription, expected.ShortDescription); + Assert.Equal(result.Phone, expected.Phone); + Assert.Equal(result.Email, expected.Email); + Assert.Equal(result.Website, expected.Website); + } + + [Fact] + public async Task GetSolicitorInfoByIdAsync_Should_MapCities_When_CitiesArePresent() + { + //arrange + var cts = new CancellationTokenSource(); + var id = Guid.NewGuid(); + + var expected = new Solicitor + { + Name = "Solicitor", + RelativeUrl = "/-some-solicitor.html" + }; + + expected.Cities.Add(new City { Name = "London" }); + + _mockRepo + .Setup(x => x.GetSolicitorByIdAsync(It.IsAny(), cts.Token)) + .ReturnsAsync(expected); + // expected.Ratings.Add(new SolicitorRating { Provider = "Google", Value = 4, Maximum = 5, Solicitor = expected }); + + + //act + var result = await _service.GetSolicitorInfoByIdAsync(id, cts.Token); + + //assert + Assert.NotNull(result); + Assert.Collection(result.Cities, city => Assert.Equal("London", city)); + } + + [Fact] + public async Task GetSolicitorInfoByIdAsync_Should_MapRatings_When_RatingsArePresent() + { + //arrange + var cts = new CancellationTokenSource(); + var id = Guid.NewGuid(); + + var expected = new Solicitor + { + Name = "Solicitor", + RelativeUrl = "/-some-solicitor.html" + }; + expected.Ratings.Add(new SolicitorRating { Provider = "Google", Value = 4, Maximum = 5, Solicitor = expected }); + + _mockRepo + .Setup(x => x.GetSolicitorByIdAsync(It.IsAny(), cts.Token)) + .ReturnsAsync(expected); + + //act + var result = await _service.GetSolicitorInfoByIdAsync(id, cts.Token); + + //assert + Assert.NotNull(result); + Assert.Collection(result.Ratings, rating => + { + Assert.Equal("Google", rating.Provider); + Assert.Equal(4, rating.Value); + Assert.Equal(5, rating.Maximum); + }); + } +} \ No newline at end of file diff --git a/backend/tests/Solicitors.Core.Tests/Solicitors.Core.Tests.csproj b/backend/tests/Solicitors.Core.Tests/Solicitors.Core.Tests.csproj new file mode 100644 index 0000000..db4ee05 --- /dev/null +++ b/backend/tests/Solicitors.Core.Tests/Solicitors.Core.Tests.csproj @@ -0,0 +1,26 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/.yarn/install-state.gz b/frontend/.yarn/install-state.gz new file mode 100644 index 0000000..1a7cf7b Binary files /dev/null and b/frontend/.yarn/install-state.gz differ diff --git a/frontend/.yarnrc.yml b/frontend/.yarnrc.yml new file mode 100644 index 0000000..063ed60 --- /dev/null +++ b/frontend/.yarnrc.yml @@ -0,0 +1,8 @@ +approvedGitRepositories: + - "**" + +enableScripts: true + +nodeLinker: node-modules + +npmMinimalAgeGate: 0 diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..5f3ba6a --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,15 @@ +FROM node:26.3.1-alpine3.24 as build + +ARG VITE_API_URL +ENV VITE_API_URL=$VITE_API_URL + +RUN mkdir -p /code +WORKDIR /code +ADD . /code +RUN npm install -g -s --no-progress corepack +RUN COREPACK_ENABLE_DOWNLOAD_PROMPT=0 /bin/sh -c "yarn -v" +RUN yarn install +RUN yarn run build + +FROM httpd:2.4.68-alpine3.24 +COPY --from=build /code/dist /usr/local/apache2/htdocs/ \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..51bbeef --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,15 @@ +# `create-preact` + +

+ +

+ +

Get started using Preact and Vite!

+ +## Getting Started + +- `yarn dev` - Starts a dev server at http://localhost:5173/ + +- `yarn build` - Builds for production, emitting to `dist/` + +- `yarn preview` - Starts a server at http://localhost:4173/ to test production build locally diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..170c101 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,17 @@ + + + + + + + + + + Vite + Preact + + + +
+ + + diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..75419b2 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,31 @@ +{ + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@babel/core": "^8.0.1", + "@base-ui/react": "^1.6.0", + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@fontsource/roboto": "^5.2.10", + "@mui/icons-material": "^9.1.1", + "@mui/material": "^9.1.2", + "axios": "^1.18.1", + "preact": "^10.26.9" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.2", + "eslint": "^10.5.0", + "eslint-config-preact": "^2.0.0", + "typescript": "^6.0.3", + "vite": "^7.0.4" + }, + "eslintConfig": { + "extends": "preact" + }, + "packageManager": "yarn@4.17.0" +} diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg new file mode 100644 index 0000000..ffcb6bc --- /dev/null +++ b/frontend/public/vite.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/preact.svg b/frontend/src/assets/preact.svg new file mode 100644 index 0000000..f34e939 --- /dev/null +++ b/frontend/src/assets/preact.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/frontend/src/components/detail.tsx b/frontend/src/components/detail.tsx new file mode 100644 index 0000000..4ab3d2b --- /dev/null +++ b/frontend/src/components/detail.tsx @@ -0,0 +1,138 @@ +import Box from "@mui/material/Box"; +import Modal from "@mui/material/Modal"; +import { forwardRef, useEffect, useState } from "react"; +import { SolicitorInfo } from "../models/solicitorSummary"; +import getSolicitor from "../functions/getSolicitor"; +import Card from "@mui/material/Card"; +import { Grid, Rating, Typography } from "@mui/material"; +import capitaliseFirstLetter from "../functions/capitaliseFirstLetter"; + +interface ClosedProps { + open : false; +} + +interface OpenProps { + open : true; + id : string; +} + +export type DetailInfo = OpenProps | ClosedProps; +type DetailProps = DetailInfo & { onClose : (error? : string) => void }; + + +export default function Detail(props : DetailProps) { + const [data, setData] = useState('loading'); + + const handleResult = (result : SolicitorInfo | undefined) => { + if (data == 'loading' && props.open && props.id == result?.id) { + setData(result); + } else if (!result) { + handleClose('An error occurred loading the solicitor'); + } + } + + const handleClose = (error? : string) => { + props.onClose(error); + setData('loading'); + } + + useEffect(() => { + if (props.open && data == 'loading') { + getSolicitor(props.id) + .then(result => handleResult(result)); + } + }, [props.open]); + + useEffect(() => {}, [data]) + + return ( + handleClose()} + > + + + + ); +} + +const style = { + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: 800, + maxWidth: '60%', + maxHeight: '80%', + bgcolor: 'background.default', + overflow: 'scroll', + p: 4, +}; + +const DetailDisplay = forwardRef((props : {data : SolicitorInfo | 'loading'}) => { + if (props.data === 'loading') { + return ( + + Waiting... + + ); + } else { + return ( + + {props.data.name} + {props.data.shortDescription} + {props.data.ratings && + (<> +
+ Ratings + + {props.data.ratings.map(rating => ( + + + {capitaliseFirstLetter(rating.provider)} + + + + ))} + + ) + } + {props.data.locations && + (<> +
+ Offices + + {props.data.locations.map(office => ( + + + {office.address.split('\n').map(line => ({line}))} + {office.phone && <>
tel: {office.phone}} + {office.ratings && office.ratings.map(rating => + (<> + {capitaliseFirstLetter(rating.provider)} + + ) + )} +
+
+ ))} +
+ ) + } + +
+ ); + } +}); \ No newline at end of file diff --git a/frontend/src/components/header.tsx b/frontend/src/components/header.tsx new file mode 100644 index 0000000..41cd118 --- /dev/null +++ b/frontend/src/components/header.tsx @@ -0,0 +1,42 @@ +import DarkMode from "@mui/icons-material/DarkMode"; +import LightMode from "@mui/icons-material/LightMode"; +import { AppBar, IconButton, Toolbar, Tooltip, Typography } from "@mui/material"; +import { Theme } from "@mui/material/styles"; + +export interface ThemeWrapper { + theme : Theme, + value : 'dark' | 'light'; +} + +interface HeaderProps { + theme : ThemeWrapper; + toggleTheme : () => void; +} + +export default function Header(props : HeaderProps) { + return ( + + + + Conveyancing Search + + + + { + props.theme.value === 'dark' + ? + : + } + + + + + ); +} \ No newline at end of file diff --git a/frontend/src/components/numberField.tsx b/frontend/src/components/numberField.tsx new file mode 100644 index 0000000..75794f9 --- /dev/null +++ b/frontend/src/components/numberField.tsx @@ -0,0 +1,114 @@ +import * as React from 'react'; +import { NumberField as BaseNumberField } from '@base-ui/react/number-field'; +import IconButton from '@mui/material/IconButton'; +import FormControl from '@mui/material/FormControl'; +import FormHelperText from '@mui/material/FormHelperText'; +import OutlinedInput from '@mui/material/OutlinedInput'; +import InputAdornment from '@mui/material/InputAdornment'; +import InputLabel from '@mui/material/InputLabel'; +import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; + +/** + * This component is a placeholder for FormControl to correctly set the shrink label state on SSR. + */ +function SSRInitialFilled(_: BaseNumberField.Root.Props) { + return null; +} +SSRInitialFilled.muiName = 'Input'; + +export default function NumberField({ + id: idProp, + label, + error, + size = 'medium', + ...other +}: BaseNumberField.Root.Props & { + label?: React.ReactNode; + size?: 'small' | 'medium'; + error?: boolean; +}) { + let id = React.useId(); + if (idProp) { + id = idProp; + } + return ( + ( + + {props.children} + + )} + > + + {label} + ( + + } + > + + + + } + > + + + + } + sx={{ pr: 0 }} + /> + )} + /> + + Enter value between 10 and 40 + + + ); +} \ No newline at end of file diff --git a/frontend/src/components/result.tsx b/frontend/src/components/result.tsx new file mode 100644 index 0000000..aebd0f8 --- /dev/null +++ b/frontend/src/components/result.tsx @@ -0,0 +1,44 @@ +import ListItemText from "@mui/material/ListItemText"; +import SolicitorSummary from "../models/solicitorSummary"; +import ListItemButton from "@mui/material/ListItemButton"; +import Typography from "@mui/material/Typography"; +import Rating from "@mui/material/Rating"; +import Divider from "@mui/material/Divider"; + +type ResultProps = SolicitorSummary & { last : boolean, onClick: () => void }; + +export default function Result(props : ResultProps) { + return ( + <> + + + + {props.name} + + {props.rating && + + } + + } + secondary={props.shortDescription} + /> + + {props.last || } + + ); +} \ No newline at end of file diff --git a/frontend/src/components/results.tsx b/frontend/src/components/results.tsx new file mode 100644 index 0000000..4085579 --- /dev/null +++ b/frontend/src/components/results.tsx @@ -0,0 +1,46 @@ +import { useEffect, useState } from "preact/hooks"; +import GetSolicitors from "../functions/getSolicitors"; +import Grid from "@mui/material/Grid"; +import Sidebar from "./sidebar"; +import FilterState from "../models/filterState"; +import { default as ResultsType, waitingResults } from "../models/results"; +import ResultsList from "./resultsList"; + +export default function Results() { + const [results, setResults] = useState(waitingResults); + const [filters, setFilters] = useState({ + cities: [], + minRating: 3, + resultsPerPage: 20, + currentPage: 1, + ratingsProvider: 'Solicitors.com', + sortBy: 'RatingDescending', + namePart: '', + nameOnly: false}); + + useEffect(() => { + if (results.type == 'Waiting') { + GetSolicitors(filters) + .then(result => setResults({type: 'Success', data: result})); + } + }, [results]); + + useEffect(() => { + setResults({type: 'Waiting'}); + }, [filters]); + + return ( + + + + + + + + + ); +} + diff --git a/frontend/src/components/resultsList.tsx b/frontend/src/components/resultsList.tsx new file mode 100644 index 0000000..7b911bb --- /dev/null +++ b/frontend/src/components/resultsList.tsx @@ -0,0 +1,42 @@ +import List from "@mui/material/List"; +import Pagination from "@mui/material/Pagination"; +import FilterState from "../models/filterState"; +import Results from "../models/results"; +import Result from "./result"; +import Detail, { DetailInfo } from "./detail"; +import { useState } from "react"; + +interface ResultsListProps { + results : Results; + filters : FilterState; + setFilters : (state : FilterState) => void; +} + +export default function ResultsList(props : ResultsListProps) { + const [detail, setDetail] = useState({open: false}); + + switch (props.results.type) { + case 'Success': + let data = props.results.data.data; + return ( + <> + + {data.map((r, i) => ( setDetail({open : true, id : r.id})} last={i == data.length - 1} />))} + + setDetail({open: false})} /> + props.setFilters(Object.assign({}, {...props.filters}, {currentPage: page}))} + /> + + ); + case 'Waiting': + return
Waiting...
; + case 'Error': + return
Error...
; + case 'Warning': + return
Warning...
; + } +} \ No newline at end of file diff --git a/frontend/src/components/sidebar.tsx b/frontend/src/components/sidebar.tsx new file mode 100644 index 0000000..a651fd0 --- /dev/null +++ b/frontend/src/components/sidebar.tsx @@ -0,0 +1,159 @@ +import { Autocomplete, Box, Divider, Grid, InputLabel, MenuItem, Paper, Rating, Select, SelectChangeEvent, Switch, TextField, ToggleButton, Typography } from "@mui/material"; +import getCities from "../functions/getCities"; +import FilterState from "../models/filterState"; +import { useEffect, useState } from "react"; +import NumberField from "./numberField"; +import getRatingsProviders from "../functions/getRatingsProviders"; +import capitaliseFirstLetter from "../functions/capitaliseFirstLetter"; + +interface SidebarProps { + filters : FilterState; + onChange : (filters : FilterState) => void; +} + +export default function Sidebar(props : SidebarProps) { + const [cities, setCities] = useState([]); + const [ratingsProviders, setRatingsProviders] = useState(['Solicitors.com']); + + useEffect(() => { + if (cities.length === 0) { + getCities().then((results) => setCities(results)); + } + }, [cities.length]); + + useEffect(() => { + if (cities.length === 0) { + getRatingsProviders().then((results) => setRatingsProviders(results)); + } + }, [cities.length]); + + return ( + + + Filter Conveyancers + + + Firm Name + props.onChange(Object.assign({}, {...props.filters}, {namePart: (e.target as any).value, currentPage: 1}))} + /> + + + + Search by Firm Name Only + props.onChange(Object.assign({}, {...props.filters}, {nameOnly: value, currentPage: 1}))} + /> + + + {!props.filters.nameOnly && + <> + + Cities + props.onChange(Object.assign({}, {...props.filters}, {cities: value, currentPage: 1}))} + renderInput={(params) => ( + + )} + /> + + + + + + Min. Rating + props.onChange(Object.assign({}, {...props.filters}, {minRating: value, currentPage: 1}))} + /> + + + Rating Provider + + + + + + } + + Results per Page + props.onChange(Object.assign({}, {...props.filters}, {resultsPerPage: value, currentPage: 1}))} + min={10} + max={40} + /> + + + + Sort By + + + + ) +} + +const sortingOptions = [ + { + value: 'RatingDescending', + name: 'Rating (descending)' + }, + { + value: 'RatingAscending', + name: 'Rating (ascending)' + }, + { + value: 'AlphabetAscending', + name: 'Alphabetical (ascending)' + }, + { + value: 'AlphabetDescending', + name: 'Alphabetical (descending)' + } +] \ No newline at end of file diff --git a/frontend/src/functions/axios.ts b/frontend/src/functions/axios.ts new file mode 100644 index 0000000..a801971 --- /dev/null +++ b/frontend/src/functions/axios.ts @@ -0,0 +1,7 @@ +import axios from 'axios'; + +const api = axios.create({ + baseURL: import.meta.env.VITE_API_URL +}); + +export default api; \ No newline at end of file diff --git a/frontend/src/functions/capitaliseFirstLetter.ts b/frontend/src/functions/capitaliseFirstLetter.ts new file mode 100644 index 0000000..33b0f19 --- /dev/null +++ b/frontend/src/functions/capitaliseFirstLetter.ts @@ -0,0 +1,3 @@ +export default function capitaliseFirstLetter(val : string) { + return String(val).charAt(0).toUpperCase() + String(val).slice(1); +} \ No newline at end of file diff --git a/frontend/src/functions/getCities.ts b/frontend/src/functions/getCities.ts new file mode 100644 index 0000000..0d1a36e --- /dev/null +++ b/frontend/src/functions/getCities.ts @@ -0,0 +1,6 @@ +import api from "./axios"; + +export default async function GetCiries() { + const response = await api.get('/cities'); + return response.data as string[]; +} \ No newline at end of file diff --git a/frontend/src/functions/getRatingsProviders.ts b/frontend/src/functions/getRatingsProviders.ts new file mode 100644 index 0000000..9efc592 --- /dev/null +++ b/frontend/src/functions/getRatingsProviders.ts @@ -0,0 +1,6 @@ +import api from "./axios"; + +export default async function GetCiries() { + const response = await api.get('/ratingsProviders'); + return response.data as string[]; +} \ No newline at end of file diff --git a/frontend/src/functions/getSolicitor.ts b/frontend/src/functions/getSolicitor.ts new file mode 100644 index 0000000..6c32363 --- /dev/null +++ b/frontend/src/functions/getSolicitor.ts @@ -0,0 +1,7 @@ +import { SolicitorInfo } from "../models/solicitorSummary"; +import api from "./axios"; + +export default async function GetSolicitor(id : string) : Promise { + const response = await api.get(`/solicitors/${id}`); + return response.data as SolicitorInfo; +} \ No newline at end of file diff --git a/frontend/src/functions/getSolicitors.ts b/frontend/src/functions/getSolicitors.ts new file mode 100644 index 0000000..1121610 --- /dev/null +++ b/frontend/src/functions/getSolicitors.ts @@ -0,0 +1,32 @@ +import SolicitorSummary from "../models/solicitorSummary"; +import PaginationResponse from "../models/paginationResponse"; +import FilterState from "../models/filterState"; +import api from "./axios"; + +export default async function GetSolicitors(filters : FilterState) { + + let queryString = `pageSize=${filters.resultsPerPage}&pageNumber=${filters.currentPage}`; + + if (filters.namePart != null && filters.namePart !== '') { + queryString += `&nameFilter=${filters.namePart}`; + } + + if (!filters.nameOnly) { + var minRating = filters.minRating; + if (minRating == null) { + minRating = 0; + } + queryString += `&minRating=${minRating}&ratingsProvider=${filters.ratingsProvider}`; + + if (filters.cities.length > 0) { + queryString += '&cities=' + filters.cities.join('&cities='); + } + } + + if (filters.sortBy != null) { + queryString += `&orderingType=${filters.sortBy}`; + } + + const response = await api.get(`/solicitors?${queryString}`); + return response.data as PaginationResponse; +} \ No newline at end of file diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx new file mode 100644 index 0000000..89568a2 --- /dev/null +++ b/frontend/src/index.tsx @@ -0,0 +1,129 @@ +import { render } from 'preact'; +import Button from '@mui/material/Button'; +import AppBar from '@mui/material/AppBar'; +import Toolbar from '@mui/material/Toolbar'; +import IconButton from '@mui/material/IconButton'; +import Typography from '@mui/material/Typography'; +import Container from '@mui/material/Container'; +import { useState } from 'preact/compat'; +import { createTheme, Theme, ThemeProvider } from '@mui/material/styles'; +import DarkMode from '@mui/icons-material/DarkMode'; +import LightMode from '@mui/icons-material/LightMode'; +import { CssBaseline, Tooltip } from '@mui/material'; +import Header, { ThemeWrapper } from './components/header'; +import Results from './components/results'; + +function prefersDarkMode() { + const darkModeMql = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)'); + return darkModeMql && darkModeMql.matches; +} + +const darkTheme = createTheme({ + palette: { + mode: 'dark', + primary: { + light: '#B79AD5', + main: '#A27EC9', + dark: '#8D61BD' + }, + secondary: { + light: '#70DBFF', + main: '#47D1FF', + dark: '#1FC7FF' + }, + error: { + light: '#E73936', + main: '#DD1C1A', + dark: '#B71815' + }, + warning: { + light: '#FA804C', + main: '#F9611F', + dark: '#EF4C06' + }, + info : { + light: '#FFEB99', + main: '#FFE066', + dark: '#FFDA47' + }, + success: { + light: '#6ADC98', + main: '#48D480', + dark: '#2FC66B' + }, + background: { + default: '#1D2535', + paper: '#242E42' + } + } +}); + +const lightTheme = createTheme({ + palette: { + mode: 'light', + primary: { + light: '#B79AD5', + main: '#A27EC9', + dark: '#8D61BD' + }, + secondary: { + light: '#70DBFF', + main: '#47D1FF', + dark: '#1FC7FF' + }, + error: { + light: '#E73936', + main: '#DD1C1A', + dark: '#B71815' + }, + warning: { + light: '#FA804C', + main: '#F9611F', + dark: '#EF4C06' + }, + info : { + light: '#FFEB99', + main: '#FFE066', + dark: '#FFDA47' + }, + success: { + light: '#6ADC98', + main: '#48D480', + dark: '#2FC66B' + }, + background: { + default: '#EFFFFF', + paper: '#e8f1f1' + } + } +}); + +export function App() { + const [value, setValue] = useState(0); + const [theme, setTheme] = useState({ + theme : prefersDarkMode() ? darkTheme : lightTheme, + value : prefersDarkMode() ? 'dark' : 'light' + }); + + const handleChange = (event: any, newValue: number) => { + setValue(newValue); + }; + + const toggleTheme = () => { + setTheme(theme.value == 'dark' + ? { theme : lightTheme, value : 'light' } + : { theme : darkTheme, value : 'dark' }); + } + + return ( + + +
+ + + + + ); +} + +render(, document.getElementById('app')); diff --git a/frontend/src/models/filterState.ts b/frontend/src/models/filterState.ts new file mode 100644 index 0000000..25e351f --- /dev/null +++ b/frontend/src/models/filterState.ts @@ -0,0 +1,10 @@ +export default interface FilterState { + cities : string[]; + minRating : number; + resultsPerPage : number; + currentPage : number; + ratingsProvider : string; + sortBy : string; + namePart : string; + nameOnly : boolean; +}; \ No newline at end of file diff --git a/frontend/src/models/paginationResponse.ts b/frontend/src/models/paginationResponse.ts new file mode 100644 index 0000000..c7dc57e --- /dev/null +++ b/frontend/src/models/paginationResponse.ts @@ -0,0 +1,4 @@ +export default interface PaginationResponse { + data : T[]; + total : number; +} \ No newline at end of file diff --git a/frontend/src/models/results.ts b/frontend/src/models/results.ts new file mode 100644 index 0000000..20381b7 --- /dev/null +++ b/frontend/src/models/results.ts @@ -0,0 +1,25 @@ +import PaginationResponse from "./paginationResponse"; +import SolicitorSummary from "./solicitorSummary"; + +type Results = SuccessResults | ErrorResults | WaitingResults; + +interface SuccessResults { + type : 'Success', + data : PaginationResponse +}; + +interface ErrorResults { + type : 'Error' | 'Warning', + message : string +}; + +interface WaitingResults { + type : 'Waiting' +}; + +const waitingResults : WaitingResults = { + type : 'Waiting' +}; + +export default Results; +export {waitingResults}; \ No newline at end of file diff --git a/frontend/src/models/solicitorSummary.ts b/frontend/src/models/solicitorSummary.ts new file mode 100644 index 0000000..f56b5b9 --- /dev/null +++ b/frontend/src/models/solicitorSummary.ts @@ -0,0 +1,30 @@ +export default interface SolicitorSummary { + name : string, + shortDescription : string | null, + id : string, + rating : Rating | null +}; + +export interface Rating { + value : number, + maximum : number, + provider : string +} + +export interface Location { + address : string, + phone : string, + ratings : Rating[] +} + +export interface SolicitorInfo { + name : string, + shortDescription : string | null, + id : string, + phone? : string, + email? : string, + website? : string, + + ratings : Rating[], + locations : Location[] +} \ No newline at end of file diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..12bb30b --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + + /* Preact Config */ + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": ["node_modules/vite/client.d.ts", "**/*"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..0e309b2 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import preact from '@preact/preset-vite'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}); diff --git a/frontend/yarn.lock b/frontend/yarn.lock new file mode 100644 index 0000000..160358a --- /dev/null +++ b/frontend/yarn.lock @@ -0,0 +1,4693 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 10 + cacheKey: 10c0 + +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/code-frame@npm:7.29.7" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.29.7" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.1.1" + checksum: 10c0/169fc2080169a40c1760155eaaaf739bcb882df0bec76a83adbda5493645bc17270a3434b8848c494b1933e96fe1d147370001e3cda09a39f43ae30f08ef2069 + languageName: node + linkType: hard + +"@babel/code-frame@npm:^8.0.0": + version: 8.0.0 + resolution: "@babel/code-frame@npm:8.0.0" + dependencies: + "@babel/helper-validator-identifier": "npm:^8.0.0" + js-tokens: "npm:^10.0.0" + checksum: 10c0/2ea8614018bcd8c86358ab6aa930508115d2205e5e111f736d355b150453818bdf487850f6dec5ec92a10bf27ba4536f4b3b8ad9204514e43d8d68ff2e542443 + languageName: node + linkType: hard + +"@babel/compat-data@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/compat-data@npm:7.29.7" + checksum: 10c0/47913f05e08a45a1c9df38c02b4b49e391005085b489432647a1abe112e5d9c75e3be8ea5972b7f6da4ec5d1339922ceb9ea02b8a25d4ed1cb8636e5261f344e + languageName: node + linkType: hard + +"@babel/compat-data@npm:^8.0.0": + version: 8.0.0 + resolution: "@babel/compat-data@npm:8.0.0" + checksum: 10c0/8d934415a308cf6c893d815b1dbb79faf7678c1e489934e81025c61673bf98ef88770b42297492bbd4c014978688012aa635e855bf8553b96ec8287bddfbae89 + languageName: node + linkType: hard + +"@babel/core@npm:^7.13.16, @babel/core@npm:^7.22.1": + version: 7.29.7 + resolution: "@babel/core@npm:7.29.7" + dependencies: + "@babel/code-frame": "npm:^7.29.7" + "@babel/generator": "npm:^7.29.7" + "@babel/helper-compilation-targets": "npm:^7.29.7" + "@babel/helper-module-transforms": "npm:^7.29.7" + "@babel/helpers": "npm:^7.29.7" + "@babel/parser": "npm:^7.29.7" + "@babel/template": "npm:^7.29.7" + "@babel/traverse": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + "@jridgewell/remapping": "npm:^2.3.5" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10c0/112fb09c24de7a1de64d1de2c31fe65c4e6af4cb2fb6e6d99ea5373e6fc51e75b88581c0efae4c4c68f119a02a988c7106e95011a41530a2fb8ed793c7eaa07b + languageName: node + linkType: hard + +"@babel/core@npm:^8.0.1": + version: 8.0.1 + resolution: "@babel/core@npm:8.0.1" + dependencies: + "@babel/code-frame": "npm:^8.0.0" + "@babel/generator": "npm:^8.0.0" + "@babel/helper-compilation-targets": "npm:^8.0.0" + "@babel/helpers": "npm:^8.0.0" + "@babel/parser": "npm:^8.0.0" + "@babel/template": "npm:^8.0.0" + "@babel/traverse": "npm:^8.0.0" + "@babel/types": "npm:^8.0.0" + "@types/gensync": "npm:^1.0.5" + convert-source-map: "npm:^2.0.0" + empathic: "npm:^2.0.1" + gensync: "npm:^1.0.0-beta.2" + import-meta-resolve: "npm:^4.2.0" + json5: "npm:^2.2.3" + obug: "npm:^2.1.1" + semver: "npm:^7.7.3" + checksum: 10c0/e7fd9266d22906132fb052383a624358f54268dade31d50264416afeab6e5648cd23c7e845a89a501d270b80601d6e89abc67de0a9f1f55133f4abd8ac745be7 + languageName: node + linkType: hard + +"@babel/eslint-parser@npm:^7.27.5": + version: 7.29.7 + resolution: "@babel/eslint-parser@npm:7.29.7" + dependencies: + "@nicolo-ribaudo/eslint-scope-5-internals": "npm:5.1.1-v1" + eslint-visitor-keys: "npm:^2.1.0" + semver: "npm:^6.3.1" + peerDependencies: + "@babel/core": ^7.11.0 + eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 + checksum: 10c0/c24dcd941b0a149b197a9e9c3297a01d621775ac71cdf2e1134b1097c1691b46ce969355e1be3676d83b88f62c43ed33cf3a499da21ea11a5eb07339ce64ac20 + languageName: node + linkType: hard + +"@babel/generator@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/generator@npm:7.29.7" + dependencies: + "@babel/parser": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10c0/9bf72b01b5bd0ea5b1288a0e37dbd360bff2f2b1ce73342c0d40fb3db2ec3dc004ada5ffa925c5e12939a416eed59e600d562b8ecd938ce0d27dfd0eb6c6c2b7 + languageName: node + linkType: hard + +"@babel/generator@npm:^8.0.0": + version: 8.0.0 + resolution: "@babel/generator@npm:8.0.0" + dependencies: + "@babel/parser": "npm:^8.0.0" + "@babel/types": "npm:^8.0.0" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + "@types/jsesc": "npm:^2.5.0" + jsesc: "npm:^3.0.2" + checksum: 10c0/2b2d171beaedcacca62164f0f39bc8962df5f9700a54c5515174276ea20b3a53933e9804e0b35c2be1059108a764447d99d0c5beb63ecbbf8c66035ca6c003c4 + languageName: node + linkType: hard + +"@babel/helper-annotate-as-pure@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-annotate-as-pure@npm:7.29.7" + dependencies: + "@babel/types": "npm:^7.29.7" + checksum: 10c0/c56536b52d17632d89d49db2063ed6102f0e3bbadf6a0ccb74e6599d6a77173b644c7fe8c3ef17c7a162709d55b75ee5145ef6db917d16ba7f375fbffcf2e942 + languageName: node + linkType: hard + +"@babel/helper-compilation-targets@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-compilation-targets@npm:7.29.7" + dependencies: + "@babel/compat-data": "npm:^7.29.7" + "@babel/helper-validator-option": "npm:^7.29.7" + browserslist: "npm:^4.24.0" + lru-cache: "npm:^5.1.1" + semver: "npm:^6.3.1" + checksum: 10c0/4c15fd4c69a0a7047799a28a88460c19cede0a0ee8af994ea169114986f4af48b92c7393a4a3fee0456c11a656eece3448a6ed06354453d6c27cccf17195453b + languageName: node + linkType: hard + +"@babel/helper-compilation-targets@npm:^8.0.0": + version: 8.0.0 + resolution: "@babel/helper-compilation-targets@npm:8.0.0" + dependencies: + "@babel/compat-data": "npm:^8.0.0" + "@babel/helper-validator-option": "npm:^8.0.0" + browserslist: "npm:^4.24.0" + lru-cache: "npm:^11.0.0" + semver: "npm:^7.7.3" + checksum: 10c0/871876b4a9de1a2be68f2ec6c7576933dd9a8452bfb3c02ebcc16939791b8f2ab7467686e40060c6abfbb44bd76f898972e53034c3e7626a686a656f3a8d3bc9 + languageName: node + linkType: hard + +"@babel/helper-globals@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-globals@npm:7.29.7" + checksum: 10c0/f38417c40b1129a1b2b519ca961b9040c8827d1444fd74068702286b91b77089431dc76b6b9d5c1496e5da2a4f3ad329c6946e688ba3fa0d1d0b3d2b4f34f36a + languageName: node + linkType: hard + +"@babel/helper-globals@npm:^8.0.0": + version: 8.0.0 + resolution: "@babel/helper-globals@npm:8.0.0" + checksum: 10c0/c53a9fcf451df5c8d3aa72f282d1f058e2874e2ccbbdbb5572a805595d75a9355461822d01a6cac3128f5d9ab9a3a99538c6be4ebd728068afb20a65a4d1d986 + languageName: node + linkType: hard + +"@babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-module-imports@npm:7.29.7" + dependencies: + "@babel/traverse": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + checksum: 10c0/6adf60d97356027413342a092f818d9678c4f5caff716a33e3284b5ae14e47a9e88059d421dde4ee4894691260039a12602c0e7becadc175602194b40dfa345d + languageName: node + linkType: hard + +"@babel/helper-module-transforms@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-module-transforms@npm:7.29.7" + dependencies: + "@babel/helper-module-imports": "npm:^7.29.7" + "@babel/helper-validator-identifier": "npm:^7.29.7" + "@babel/traverse": "npm:^7.29.7" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/ee5a2172c24a42be696836f4b0d947489c9729d8adf5821885cf77d1ad5333e3c447368e9a71f67df1099570490553dccf9f888ef0a92a48aa63cb086bd8c7e1 + languageName: node + linkType: hard + +"@babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-plugin-utils@npm:7.29.7" + checksum: 10c0/380477a06133274a2759f9355929cb60a95e8b8fee624a1ae1fa349e1d1645b89daca456f72833f6d1062bffa12ee4271c5bf0cc5a61c0166cdc24c7591e2408 + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-string-parser@npm:7.29.7" + checksum: 10c0/194bc0f1716e396d5ffde56ad6119745fb9557662c98611590e5e454906783a4ccb21ce93056b8eb69a4909044834e45d96e50ac695bbe9e3221648fe033c06c + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^8.0.0": + version: 8.0.0 + resolution: "@babel/helper-string-parser@npm:8.0.0" + checksum: 10c0/b2272a9eaa63a1bc9ce2673d580e69620fd79786f3e3cf1891f406801f8211810f194f2739fef920f01a97ae06ae465b2dfd238ab8037a3182d0869939ca9a5d + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-validator-identifier@npm:7.29.7" + checksum: 10c0/4795354e7ae0dcafa72de1cd04ec51252dc1498517170beaf019e03effc5b7bf13c6b21a3949a77e07b8125be7f106ed1131350d8ebd4566ae874094a726d62b + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^8.0.0": + version: 8.0.2 + resolution: "@babel/helper-validator-identifier@npm:8.0.2" + checksum: 10c0/629eda2a0a99442a1628bbef5f9fd8e26c1aa21ddbf02760d1c482b737932b1bb21fb1f89aacf9c3cf0642b481b810e39f94b8ae9aa44fc4f5516ebec6798b7b + languageName: node + linkType: hard + +"@babel/helper-validator-option@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-validator-option@npm:7.29.7" + checksum: 10c0/d2a06c6d0ac40ba4a2f219fc2cab249c7a94bacdb2686273b7f9598571c908809b48468ff588915a346e6cc7296f60b581023d1d498b747fed06f779d335c2cc + languageName: node + linkType: hard + +"@babel/helper-validator-option@npm:^8.0.0": + version: 8.0.0 + resolution: "@babel/helper-validator-option@npm:8.0.0" + checksum: 10c0/eb466b464479f75c15ba06f6c53862f10d90740350c9203b3d81c6f33d4193bf0a7b7b89de901f256fb639a1346475b48616734af89906d6b4fcf9a856d9f1f1 + languageName: node + linkType: hard + +"@babel/helpers@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helpers@npm:7.29.7" + dependencies: + "@babel/template": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + checksum: 10c0/218e8d10953647c9f44775f5a022b227a182674853b5ea8631889deb7e1a3e4bc870388aaecf59bb8bd92a87f9a96220ed3f70a35bffec6bcf9169ecb67891ac + languageName: node + linkType: hard + +"@babel/helpers@npm:^8.0.0": + version: 8.0.0 + resolution: "@babel/helpers@npm:8.0.0" + dependencies: + "@babel/template": "npm:^8.0.0" + "@babel/types": "npm:^8.0.0" + checksum: 10c0/5d649bf2cfc37b76b8c9dd7d2992fa0503a6081c2439cd4de16b56abb778f3bb391441d687594f3e08af5ef3f59c8b93a77ae81f9e5f831989ebfd3443851189 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/parser@npm:7.29.7" + dependencies: + "@babel/types": "npm:^7.29.7" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/65133038f80b54a714d6027cb77cee3f9a6b5c4c6842ce674301e13947cbcbfa8055e63acaf1b84c085d34226a14425b2c2b97b829e0e226d2e8f1299942a51d + languageName: node + linkType: hard + +"@babel/parser@npm:^8.0.0": + version: 8.0.0 + resolution: "@babel/parser@npm:8.0.0" + dependencies: + "@babel/types": "npm:^8.0.0" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/f10665bf3289f4bdc43bc58c482d6baa9fc4e306460d1bc57b059486dc9f23ed4463e746f62d721396466e2a2e0b32d856648b8c30dedbf6b69787b430f21e75 + languageName: node + linkType: hard + +"@babel/plugin-syntax-class-properties@npm:^7.12.13": + version: 7.12.13 + resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.12.13" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/95168fa186416195280b1264fb18afcdcdcea780b3515537b766cb90de6ce042d42dd6a204a39002f794ae5845b02afb0fd4861a3308a861204a55e68310a120 + languageName: node + linkType: hard + +"@babel/plugin-syntax-jsx@npm:^7.12.13, @babel/plugin-syntax-jsx@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-syntax-jsx@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.29.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/1736000de183538ba8eef34520105508860e48b0c763254ba9158af5e814ed8bbceeedbb4281fbda33de787ae5b3870e92f60c6ae7131e7d322e451d57387896 + languageName: node + linkType: hard + +"@babel/plugin-transform-react-jsx-development@npm:^7.27.1": + version: 7.29.7 + resolution: "@babel/plugin-transform-react-jsx-development@npm:7.29.7" + dependencies: + "@babel/plugin-transform-react-jsx": "npm:^7.29.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/a2d44d068d35f8e6bc0437ba0da86526d4473491a43108caf77cba467a3ab522cbd09bcd8184b7a49f3d2b52e0883ad797ed2f91c090b857a80a6f383e88f449 + languageName: node + linkType: hard + +"@babel/plugin-transform-react-jsx@npm:^7.27.1, @babel/plugin-transform-react-jsx@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-react-jsx@npm:7.29.7" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.29.7" + "@babel/helper-module-imports": "npm:^7.29.7" + "@babel/helper-plugin-utils": "npm:^7.29.7" + "@babel/plugin-syntax-jsx": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/ae6487c3deafcffda8a2c1b1eb77e0b7121630fa1a9646cecd73dbbdd8271532a0f7f0e8c01f69b6d39a36d8d79eb67e2d51031369c9055f18f7d8e70d9b5446 + languageName: node + linkType: hard + +"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.29.2, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7": + version: 7.29.7 + resolution: "@babel/runtime@npm:7.29.7" + checksum: 10c0/ca11572f7146b21e0bde6a9ed4bb6a89eafbee5f0944c7eb54d0d8a2dac962c33638a1d611e14faa71dfbb92b4b5f9236232208568a6b7d5c6f3f39ddb91771e + languageName: node + linkType: hard + +"@babel/template@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/template@npm:7.29.7" + dependencies: + "@babel/code-frame": "npm:^7.29.7" + "@babel/parser": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + checksum: 10c0/8bb7f900dcab0e9e1c5ffbc33ca10e0d26b7b2e2ca804becb73ee771b9c4ed6e2908a4ae4a14c08560febb45d2b6b9a173955e42ad404d05f8b04840a14d9c58 + languageName: node + linkType: hard + +"@babel/template@npm:^8.0.0": + version: 8.0.0 + resolution: "@babel/template@npm:8.0.0" + dependencies: + "@babel/code-frame": "npm:^8.0.0" + "@babel/parser": "npm:^8.0.0" + "@babel/types": "npm:^8.0.0" + checksum: 10c0/8a5e5687299517cc6b0efff3a6342cf4c4256aeeffa14753cc8d08106aaf0ba1916707c0e1f385b511a7b42c6c87806fecaecd4fe06316207475e0d4d1148e2c + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/traverse@npm:7.29.7" + dependencies: + "@babel/code-frame": "npm:^7.29.7" + "@babel/generator": "npm:^7.29.7" + "@babel/helper-globals": "npm:^7.29.7" + "@babel/parser": "npm:^7.29.7" + "@babel/template": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + debug: "npm:^4.3.1" + checksum: 10c0/e256a1fbdb956555b76f3c285b1e453f6bedec8b3afb61751d99d933efd11c7d79caf5ddf2493570058a9f7deaa1b48324380d7c1aa1443fd9508becbf56331a + languageName: node + linkType: hard + +"@babel/traverse@npm:^8.0.0": + version: 8.0.0 + resolution: "@babel/traverse@npm:8.0.0" + dependencies: + "@babel/code-frame": "npm:^8.0.0" + "@babel/generator": "npm:^8.0.0" + "@babel/helper-globals": "npm:^8.0.0" + "@babel/parser": "npm:^8.0.0" + "@babel/template": "npm:^8.0.0" + "@babel/types": "npm:^8.0.0" + obug: "npm:^2.1.1" + checksum: 10c0/de5e5c144c72a2949d75ea8baf5dd2926a6410512397ac28692719df45e31e70e26415e10cacfe2c1c1dd73e7276c6a8fc832754c8b14202627d64e1aa1a9393 + languageName: node + linkType: hard + +"@babel/types@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/types@npm:7.29.7" + dependencies: + "@babel/helper-string-parser": "npm:^7.29.7" + "@babel/helper-validator-identifier": "npm:^7.29.7" + checksum: 10c0/b6623994c69717fa27294f5fa46d59140338e2d86c6c1c13085c84ef7d53086ee357fbf4fe9abe3dd3da75734dc77c4c0df2f90fb29e667558bb3b3fb705e88f + languageName: node + linkType: hard + +"@babel/types@npm:^8.0.0": + version: 8.0.0 + resolution: "@babel/types@npm:8.0.0" + dependencies: + "@babel/helper-string-parser": "npm:^8.0.0" + "@babel/helper-validator-identifier": "npm:^8.0.0" + checksum: 10c0/f4130b88818cf9fd0fb653dd35459f58794018416bc0cee0eccefd364c2d146ae90611ae3ae19d91c2f0bbd1e0e440840832baf9a3d5eb5fed077a52f0bcc001 + languageName: node + linkType: hard + +"@base-ui/react@npm:^1.6.0": + version: 1.6.0 + resolution: "@base-ui/react@npm:1.6.0" + dependencies: + "@babel/runtime": "npm:^7.29.2" + "@base-ui/utils": "npm:0.3.1" + "@floating-ui/react-dom": "npm:^2.1.8" + "@floating-ui/utils": "npm:^0.2.11" + use-sync-external-store: "npm:^1.6.0" + peerDependencies: + "@date-fns/tz": ^1.2.0 + "@types/react": ^17 || ^18 || ^19 + date-fns: ^4.0.0 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + "@date-fns/tz": + optional: true + "@types/react": + optional: true + date-fns: + optional: true + checksum: 10c0/5e5994dc782710eac900d68cc0691aea70a47fabc6ef8d2ea943103d9aa96c4b44f9fd76ba65d29fa2667a5fbd7ea7a1cc02fe111c4bf639d4e3e65a1c281118 + languageName: node + linkType: hard + +"@base-ui/utils@npm:0.3.1": + version: 0.3.1 + resolution: "@base-ui/utils@npm:0.3.1" + dependencies: + "@babel/runtime": "npm:^7.29.2" + "@floating-ui/utils": "npm:^0.2.11" + reselect: "npm:^5.2.0" + use-sync-external-store: "npm:^1.6.0" + peerDependencies: + "@types/react": ^17 || ^18 || ^19 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/4de858c3e2c8a4486a549d51c5e887bba6921e5a7f8db54439ba558feaf1656c3592a873928aee8a1711ee9210be3a65ca43b26713e97f649695ed2fa8c3af2c + languageName: node + linkType: hard + +"@emotion/babel-plugin@npm:^11.13.5": + version: 11.13.5 + resolution: "@emotion/babel-plugin@npm:11.13.5" + dependencies: + "@babel/helper-module-imports": "npm:^7.16.7" + "@babel/runtime": "npm:^7.18.3" + "@emotion/hash": "npm:^0.9.2" + "@emotion/memoize": "npm:^0.9.0" + "@emotion/serialize": "npm:^1.3.3" + babel-plugin-macros: "npm:^3.1.0" + convert-source-map: "npm:^1.5.0" + escape-string-regexp: "npm:^4.0.0" + find-root: "npm:^1.1.0" + source-map: "npm:^0.5.7" + stylis: "npm:4.2.0" + checksum: 10c0/8ccbfec7defd0e513cb8a1568fa179eac1e20c35fda18aed767f6c59ea7314363ebf2de3e9d2df66c8ad78928dc3dceeded84e6fa8059087cae5c280090aeeeb + languageName: node + linkType: hard + +"@emotion/cache@npm:^11.14.0": + version: 11.14.0 + resolution: "@emotion/cache@npm:11.14.0" + dependencies: + "@emotion/memoize": "npm:^0.9.0" + "@emotion/sheet": "npm:^1.4.0" + "@emotion/utils": "npm:^1.4.2" + "@emotion/weak-memoize": "npm:^0.4.0" + stylis: "npm:4.2.0" + checksum: 10c0/3fa3e7a431ab6f8a47c67132a00ac8358f428c1b6c8421d4b20de9df7c18e95eec04a5a6ff5a68908f98d3280044f247b4965ac63df8302d2c94dba718769724 + languageName: node + linkType: hard + +"@emotion/hash@npm:^0.9.2": + version: 0.9.2 + resolution: "@emotion/hash@npm:0.9.2" + checksum: 10c0/0dc254561a3cc0a06a10bbce7f6a997883fd240c8c1928b93713f803a2e9153a257a488537012efe89dbe1246f2abfe2add62cdb3471a13d67137fcb808e81c2 + languageName: node + linkType: hard + +"@emotion/is-prop-valid@npm:^1.3.0": + version: 1.4.0 + resolution: "@emotion/is-prop-valid@npm:1.4.0" + dependencies: + "@emotion/memoize": "npm:^0.9.0" + checksum: 10c0/5f857814ec7d8c7e727727346dfb001af6b1fb31d621a3ce9c3edf944a484d8b0d619546c30899ae3ade2f317c76390ba4394449728e9bf628312defc2c41ac3 + languageName: node + linkType: hard + +"@emotion/memoize@npm:^0.9.0": + version: 0.9.0 + resolution: "@emotion/memoize@npm:0.9.0" + checksum: 10c0/13f474a9201c7f88b543e6ea42f55c04fb2fdc05e6c5a3108aced2f7e7aa7eda7794c56bba02985a46d8aaa914fcdde238727a98341a96e2aec750d372dadd15 + languageName: node + linkType: hard + +"@emotion/react@npm:^11.14.0": + version: 11.14.0 + resolution: "@emotion/react@npm:11.14.0" + dependencies: + "@babel/runtime": "npm:^7.18.3" + "@emotion/babel-plugin": "npm:^11.13.5" + "@emotion/cache": "npm:^11.14.0" + "@emotion/serialize": "npm:^1.3.3" + "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.2.0" + "@emotion/utils": "npm:^1.4.2" + "@emotion/weak-memoize": "npm:^0.4.0" + hoist-non-react-statics: "npm:^3.3.1" + peerDependencies: + react: ">=16.8.0" + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/d0864f571a9f99ec643420ef31fde09e2006d3943a6aba079980e4d5f6e9f9fecbcc54b8f617fe003c00092ff9d5241179149ffff2810cb05cf72b4620cfc031 + languageName: node + linkType: hard + +"@emotion/serialize@npm:^1.3.3": + version: 1.3.3 + resolution: "@emotion/serialize@npm:1.3.3" + dependencies: + "@emotion/hash": "npm:^0.9.2" + "@emotion/memoize": "npm:^0.9.0" + "@emotion/unitless": "npm:^0.10.0" + "@emotion/utils": "npm:^1.4.2" + csstype: "npm:^3.0.2" + checksum: 10c0/b28cb7de59de382021de2b26c0c94ebbfb16967a1b969a56fdb6408465a8993df243bfbd66430badaa6800e1834724e84895f5a6a9d97d0d224de3d77852acb4 + languageName: node + linkType: hard + +"@emotion/sheet@npm:^1.4.0": + version: 1.4.0 + resolution: "@emotion/sheet@npm:1.4.0" + checksum: 10c0/3ca72d1650a07d2fbb7e382761b130b4a887dcd04e6574b2d51ce578791240150d7072a9bcb4161933abbcd1e38b243a6fb4464a7fe991d700c17aa66bb5acc7 + languageName: node + linkType: hard + +"@emotion/styled@npm:^11.14.1": + version: 11.14.1 + resolution: "@emotion/styled@npm:11.14.1" + dependencies: + "@babel/runtime": "npm:^7.18.3" + "@emotion/babel-plugin": "npm:^11.13.5" + "@emotion/is-prop-valid": "npm:^1.3.0" + "@emotion/serialize": "npm:^1.3.3" + "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.2.0" + "@emotion/utils": "npm:^1.4.2" + peerDependencies: + "@emotion/react": ^11.0.0-rc.0 + react: ">=16.8.0" + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/2bbf8451df49c967e41fbcf8111a7f6dafe6757f0cc113f2f6e287206c45ac1d54dc8a95a483b7c0cee8614b8a8d08155bded6453d6721de1f8cc8d5b9216963 + languageName: node + linkType: hard + +"@emotion/unitless@npm:^0.10.0": + version: 0.10.0 + resolution: "@emotion/unitless@npm:0.10.0" + checksum: 10c0/150943192727b7650eb9a6851a98034ddb58a8b6958b37546080f794696141c3760966ac695ab9af97efe10178690987aee4791f9f0ad1ff76783cdca83c1d49 + languageName: node + linkType: hard + +"@emotion/use-insertion-effect-with-fallbacks@npm:^1.2.0": + version: 1.2.0 + resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.2.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/074dbc92b96bdc09209871070076e3b0351b6b47efefa849a7d9c37ab142130767609ca1831da0055988974e3b895c1de7606e4c421fecaa27c3e56a2afd3b08 + languageName: node + linkType: hard + +"@emotion/utils@npm:^1.4.2": + version: 1.4.2 + resolution: "@emotion/utils@npm:1.4.2" + checksum: 10c0/7d0010bf60a2a8c1a033b6431469de4c80e47aeb8fd856a17c1d1f76bbc3a03161a34aeaa78803566e29681ca551e7bf9994b68e9c5f5c796159923e44f78d9a + languageName: node + linkType: hard + +"@emotion/weak-memoize@npm:^0.4.0": + version: 0.4.0 + resolution: "@emotion/weak-memoize@npm:0.4.0" + checksum: 10c0/64376af11f1266042d03b3305c30b7502e6084868e33327e944b539091a472f089db307af69240f7188f8bc6b319276fd7b141a36613f1160d73d12a60f6ca1a + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/aix-ppc64@npm:0.27.7" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/android-arm64@npm:0.27.7" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/android-arm@npm:0.27.7" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/android-x64@npm:0.27.7" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/darwin-arm64@npm:0.27.7" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/darwin-x64@npm:0.27.7" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/freebsd-arm64@npm:0.27.7" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/freebsd-x64@npm:0.27.7" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-arm64@npm:0.27.7" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-arm@npm:0.27.7" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-ia32@npm:0.27.7" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-loong64@npm:0.27.7" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-mips64el@npm:0.27.7" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-ppc64@npm:0.27.7" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-riscv64@npm:0.27.7" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-s390x@npm:0.27.7" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-x64@npm:0.27.7" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/netbsd-arm64@npm:0.27.7" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/netbsd-x64@npm:0.27.7" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/openbsd-arm64@npm:0.27.7" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/openbsd-x64@npm:0.27.7" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/openharmony-arm64@npm:0.27.7" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/sunos-x64@npm:0.27.7" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/win32-arm64@npm:0.27.7" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/win32-ia32@npm:0.27.7" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/win32-x64@npm:0.27.7" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@eslint-community/eslint-utils@npm:^4.8.0": + version: 4.9.1 + resolution: "@eslint-community/eslint-utils@npm:4.9.1" + dependencies: + eslint-visitor-keys: "npm:^3.4.3" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + checksum: 10c0/dc4ab5e3e364ef27e33666b11f4b86e1a6c1d7cbf16f0c6ff87b1619b3562335e9201a3d6ce806221887ff780ec9d828962a290bb910759fd40a674686503f02 + languageName: node + linkType: hard + +"@eslint-community/regexpp@npm:^4.12.2": + version: 4.12.2 + resolution: "@eslint-community/regexpp@npm:4.12.2" + checksum: 10c0/fddcbc66851b308478d04e302a4d771d6917a0b3740dc351513c0da9ca2eab8a1adf99f5e0aa7ab8b13fa0df005c81adeee7e63a92f3effd7d367a163b721c2d + languageName: node + linkType: hard + +"@eslint/config-array@npm:^0.23.5": + version: 0.23.5 + resolution: "@eslint/config-array@npm:0.23.5" + dependencies: + "@eslint/object-schema": "npm:^3.0.5" + debug: "npm:^4.3.1" + minimatch: "npm:^10.2.4" + checksum: 10c0/b24833c4c76e78ee075d306cd3f095db46b2db0f90cc13a6ee6e4275f9889731c05bf5403ab5fefb79c756e07ac9184ed0e04570341382f9eccbccc80e6d1a0c + languageName: node + linkType: hard + +"@eslint/config-helpers@npm:^0.6.0": + version: 0.6.0 + resolution: "@eslint/config-helpers@npm:0.6.0" + dependencies: + "@eslint/core": "npm:^1.2.1" + checksum: 10c0/f9af20e8b60b0ba27edb74b8eb40c0c5d51a9bf9baf9e053bb57833a87cb0a1c49b4dfaad88fc24d49c907ad1324c8a0b668684fa9c321351dac4bc9155ec10a + languageName: node + linkType: hard + +"@eslint/core@npm:^1.2.1": + version: 1.2.1 + resolution: "@eslint/core@npm:1.2.1" + dependencies: + "@types/json-schema": "npm:^7.0.15" + checksum: 10c0/10979b40588ecfef771fcb5013a542a35fb30692cc95a65f3481b0b36fbd89f5679efeb30d57f4eed35203d859aabace2a620177d6c536f71b299a1af2f3398f + languageName: node + linkType: hard + +"@eslint/js@npm:^9.29.0": + version: 9.39.4 + resolution: "@eslint/js@npm:9.39.4" + checksum: 10c0/5aa7dea2cbc5decf7f5e3b0c6f86a084ccee0f792d288ca8e839f8bc1b64e03e227068968e49b26096e6f71fd857ab6e42691d1b993826b9a3883f1bdd7a0e46 + languageName: node + linkType: hard + +"@eslint/object-schema@npm:^3.0.5": + version: 3.0.5 + resolution: "@eslint/object-schema@npm:3.0.5" + checksum: 10c0/1db337431f520b99e9edda64ef5fafd7ec6a029843eeb608753025125b6649d861d843cffafafd3c4e37926d7d5f9ec0c6a8e3665c13c3da2144e8132892e92e + languageName: node + linkType: hard + +"@eslint/plugin-kit@npm:^0.7.2": + version: 0.7.2 + resolution: "@eslint/plugin-kit@npm:0.7.2" + dependencies: + "@eslint/core": "npm:^1.2.1" + levn: "npm:^0.4.1" + checksum: 10c0/aafba08077bcd6d7dde6c2e21db18086046a88f914f29971a84cac9ad2d48952ded1b293e665e523805297eff756522dafa16f0062195e2c7143dcd1d47d11ed + languageName: node + linkType: hard + +"@floating-ui/core@npm:^1.7.5": + version: 1.7.5 + resolution: "@floating-ui/core@npm:1.7.5" + dependencies: + "@floating-ui/utils": "npm:^0.2.11" + checksum: 10c0/f9c52205e198b231d63a387b09c659aab08c46a1899e0b0bbe147b8b4f048b546f15ba17cb5d2a471da9534f1883d979425e13e5c4ceee67be63e4b0abd4db5d + languageName: node + linkType: hard + +"@floating-ui/dom@npm:^1.7.6": + version: 1.7.6 + resolution: "@floating-ui/dom@npm:1.7.6" + dependencies: + "@floating-ui/core": "npm:^1.7.5" + "@floating-ui/utils": "npm:^0.2.11" + checksum: 10c0/5c098e0d7b58c9bc769f276cca1766994c2c9c70c92d091a61bba8b3e9be53c011e0a79a8457fc2fb2f3d91697a26eb52e0a4962ef936dc963b45f58613c212f + languageName: node + linkType: hard + +"@floating-ui/react-dom@npm:^2.1.8": + version: 2.1.8 + resolution: "@floating-ui/react-dom@npm:2.1.8" + dependencies: + "@floating-ui/dom": "npm:^1.7.6" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 10c0/26260ca4bb23b57c73b824062505abf977a008ce6e0463bdacca74f7e49853c4cd1d2bbf1a77c6caa17fa37dfffda2c6c4cd07a8737ebd7474aaff7818401d75 + languageName: node + linkType: hard + +"@floating-ui/utils@npm:^0.2.11": + version: 0.2.11 + resolution: "@floating-ui/utils@npm:0.2.11" + checksum: 10c0/f4bcea1559bdbb721ecc8e8ead423ac58d6a5b6e70b602cf0810ba6ad4ed1c77211b207faa88b278a9042f0c743133de08a203ed6741c1b6443423332884d5b3 + languageName: node + linkType: hard + +"@fontsource/roboto@npm:^5.2.10": + version: 5.2.10 + resolution: "@fontsource/roboto@npm:5.2.10" + checksum: 10c0/abadfcaefbe2196bbd36d9258b336063f721894a3624a9639da00792583c565bfd0748ef978f91a38c3c0de8d247615c9be92a763184c085c630fa43f04b1cd9 + languageName: node + linkType: hard + +"@humanfs/core@npm:^0.19.2": + version: 0.19.2 + resolution: "@humanfs/core@npm:0.19.2" + dependencies: + "@humanfs/types": "npm:^0.15.0" + checksum: 10c0/d0a1d52d7b30c27d49475a53072d1510b81c5803e44b342fb8faf3887f1aa27593a1e6dc76a45268e7892d3f4e198146659281f6b6d55eacf3fd5a38bac30c5c + languageName: node + linkType: hard + +"@humanfs/node@npm:^0.16.6": + version: 0.16.8 + resolution: "@humanfs/node@npm:0.16.8" + dependencies: + "@humanfs/core": "npm:^0.19.2" + "@humanfs/types": "npm:^0.15.0" + "@humanwhocodes/retry": "npm:^0.4.0" + checksum: 10c0/56140579db811af4e160b195d45d0f29acf644d192c93fe24c9e594ebf06f19dfc157494a07c84540b8a071c0e4b37209c2362765d31734f4d0be869c2422e25 + languageName: node + linkType: hard + +"@humanfs/types@npm:^0.15.0": + version: 0.15.0 + resolution: "@humanfs/types@npm:0.15.0" + checksum: 10c0/fc26b9a024b0e55f7eaf64036df94345bf5d36d6a41ef80ef38e78f1f7430ce26cf435af736adae58913baae18eac3f38c18739054a3d379102015978eae862e + languageName: node + linkType: hard + +"@humanwhocodes/module-importer@npm:^1.0.1": + version: 1.0.1 + resolution: "@humanwhocodes/module-importer@npm:1.0.1" + checksum: 10c0/909b69c3b86d482c26b3359db16e46a32e0fb30bd306a3c176b8313b9e7313dba0f37f519de6aa8b0a1921349e505f259d19475e123182416a506d7f87e7f529 + languageName: node + linkType: hard + +"@humanwhocodes/retry@npm:^0.4.0, @humanwhocodes/retry@npm:^0.4.2": + version: 0.4.3 + resolution: "@humanwhocodes/retry@npm:0.4.3" + checksum: 10c0/3775bb30087d4440b3f7406d5a057777d90e4b9f435af488a4923ef249e93615fb78565a85f173a186a076c7706a81d0d57d563a2624e4de2c5c9c66c486ce42 + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.12, @jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.13 + resolution: "@jridgewell/gen-mapping@npm:0.3.13" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/9a7d65fb13bd9aec1fbab74cda08496839b7e2ceb31f5ab922b323e94d7c481ce0fc4fd7e12e2610915ed8af51178bdc61e168e92a8c8b8303b030b03489b13b + languageName: node + linkType: hard + +"@jridgewell/remapping@npm:^2.3.5": + version: 2.3.5 + resolution: "@jridgewell/remapping@npm:2.3.5" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/3de494219ffeb2c5c38711d0d7bb128097edf91893090a2dbc8ee0b55d092bb7347b1fd0f478486c5eab010e855c73927b1666f2107516d472d24a73017d1194 + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0, @jridgewell/sourcemap-codec@npm:^1.5.5": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.28": + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9 + languageName: node + linkType: hard + +"@mdn/browser-compat-data@npm:^5.6.19": + version: 5.7.6 + resolution: "@mdn/browser-compat-data@npm:5.7.6" + checksum: 10c0/918df768e734115bee0a43fb2eceaf6438cb9b294c27e13d0ac8a19ab9f00f59e4f306fc38c498b62bdec2d726d4f3874826fa2afccd2a29d50d306f687bfdff + languageName: node + linkType: hard + +"@mdn/browser-compat-data@npm:^6.1.1": + version: 6.1.5 + resolution: "@mdn/browser-compat-data@npm:6.1.5" + checksum: 10c0/a2d8ef7cdb3268c72540909755d4d56ae5a8674ddd8216a3bd4a40e4d1497c01b0205a29a8fd6bf4599e091cbc8a90d9a3a52836a8439d66b0af00886f68e7b9 + languageName: node + linkType: hard + +"@mui/core-downloads-tracker@npm:^9.1.2": + version: 9.1.2 + resolution: "@mui/core-downloads-tracker@npm:9.1.2" + checksum: 10c0/787f9bf6bc178cf405252bbd7ab8fd24375cb203fe36beedc29165eea432bf120e1fdf61b22de0037b6e2db6702c9f348c8f45164a5c367a3f3dbacea6f1fe04 + languageName: node + linkType: hard + +"@mui/icons-material@npm:^9.1.1": + version: 9.1.1 + resolution: "@mui/icons-material@npm:9.1.1" + dependencies: + "@babel/runtime": "npm:^7.29.2" + peerDependencies: + "@mui/material": ^9.1.1 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/bb7719221fed6258be9959538986aa9525370a0db25ca4acf3d58adb85a119ebc98ca519054789efffb3813aeea9c83d120b3e0358f7b67e88f73997ef33c986 + languageName: node + linkType: hard + +"@mui/material@npm:^9.1.2": + version: 9.1.2 + resolution: "@mui/material@npm:9.1.2" + dependencies: + "@babel/runtime": "npm:^7.29.2" + "@mui/core-downloads-tracker": "npm:^9.1.2" + "@mui/system": "npm:^9.1.2" + "@mui/types": "npm:^9.1.1" + "@mui/utils": "npm:^9.1.1" + "@popperjs/core": "npm:^2.11.8" + "@types/react-transition-group": "npm:^4.4.12" + clsx: "npm:^2.1.1" + csstype: "npm:^3.2.3" + prop-types: "npm:^15.8.1" + react-is: "npm:^19.2.6" + react-transition-group: "npm:^4.4.5" + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@mui/material-pigment-css": ^9.1.1 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@mui/material-pigment-css": + optional: true + "@types/react": + optional: true + checksum: 10c0/b3279d4b8f2df6c6de88b51bb236b8f800dbc83d37eb168c4525bef01990a1e4016f65868a68e0244fd01113bba9b297e9fc10c41499b1b540c4c3778b61da5e + languageName: node + linkType: hard + +"@mui/private-theming@npm:^9.1.1": + version: 9.1.1 + resolution: "@mui/private-theming@npm:9.1.1" + dependencies: + "@babel/runtime": "npm:^7.29.2" + "@mui/utils": "npm:^9.1.1" + prop-types: "npm:^15.8.1" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/edf9cd75aee97a6b95e2ea98cbb4c8d3f72db48db909c445f3129a6a5fad00ca917ec1bfdd1d107481ecc20b9c9d9560298cb588102f069c0b0a5a9c7198eb49 + languageName: node + linkType: hard + +"@mui/styled-engine@npm:^9.1.1": + version: 9.1.1 + resolution: "@mui/styled-engine@npm:9.1.1" + dependencies: + "@babel/runtime": "npm:^7.29.2" + "@emotion/cache": "npm:^11.14.0" + "@emotion/serialize": "npm:^1.3.3" + "@emotion/sheet": "npm:^1.4.0" + csstype: "npm:^3.2.3" + prop-types: "npm:^15.8.1" + peerDependencies: + "@emotion/react": ^11.4.1 + "@emotion/styled": ^11.3.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + checksum: 10c0/250bb2a94ac3399a3a9c2ba81871419100fed51b823b87a935215c50781aace3022585697039898073964e661e749abd9e30a6a241ce7567ad5eec47ef082467 + languageName: node + linkType: hard + +"@mui/system@npm:^9.1.2": + version: 9.1.2 + resolution: "@mui/system@npm:9.1.2" + dependencies: + "@babel/runtime": "npm:^7.29.2" + "@mui/private-theming": "npm:^9.1.1" + "@mui/styled-engine": "npm:^9.1.1" + "@mui/types": "npm:^9.1.1" + "@mui/utils": "npm:^9.1.1" + clsx: "npm:^2.1.1" + csstype: "npm:^3.2.3" + prop-types: "npm:^15.8.1" + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@types/react": + optional: true + checksum: 10c0/9f274cf009e4235a9bbb9a6524aa2de6ba59a3fc01e91157004be1a4d5c6d4db5f16d91dfe95d3c3acb36fa355a8ac7d811b146a547f3b9292c2a1c981adb707 + languageName: node + linkType: hard + +"@mui/types@npm:^9.1.1": + version: 9.1.1 + resolution: "@mui/types@npm:9.1.1" + dependencies: + "@babel/runtime": "npm:^7.29.2" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/83cb89cafba0c55c909a8a89bd165550a71f92a67bbd191823b6ec304e5f59b43438052fecc4f69cc27e4e557c6bce3d5a073b9f5d5787d62d8cdc055e479905 + languageName: node + linkType: hard + +"@mui/utils@npm:^9.1.1": + version: 9.1.1 + resolution: "@mui/utils@npm:9.1.1" + dependencies: + "@babel/runtime": "npm:^7.29.2" + "@mui/types": "npm:^9.1.1" + "@types/prop-types": "npm:^15.7.15" + clsx: "npm:^2.1.1" + prop-types: "npm:^15.8.1" + react-is: "npm:^19.2.6" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/a39936fa287ba3f53c47ef3072b813b797a75b43ed95212e817e9d3828e2de8079ae834061fc7e76b096eb0ceddd39468f4f0423ac25b6ac39aa77d70945966b + languageName: node + linkType: hard + +"@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1": + version: 5.1.1-v1 + resolution: "@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1" + dependencies: + eslint-scope: "npm:5.1.1" + checksum: 10c0/75dda3e623b8ad7369ca22552d6beee337a814b2d0e8a32d23edd13fcb65c8082b32c5d86e436f3860dd7ade30d91d5db55d4ef9a08fb5a976c718ecc0d88a74 + languageName: node + linkType: hard + +"@popperjs/core@npm:^2.11.8": + version: 2.11.8 + resolution: "@popperjs/core@npm:2.11.8" + checksum: 10c0/4681e682abc006d25eb380d0cf3efc7557043f53b6aea7a5057d0d1e7df849a00e281cd8ea79c902a35a414d7919621fc2ba293ecec05f413598e0b23d5a1e63 + languageName: node + linkType: hard + +"@preact/preset-vite@npm:^2.10.2": + version: 2.10.5 + resolution: "@preact/preset-vite@npm:2.10.5" + dependencies: + "@babel/plugin-transform-react-jsx": "npm:^7.27.1" + "@babel/plugin-transform-react-jsx-development": "npm:^7.27.1" + "@prefresh/vite": "npm:^2.4.11" + "@rollup/pluginutils": "npm:^5.0.0" + babel-plugin-transform-hook-names: "npm:^1.0.2" + debug: "npm:^4.4.3" + magic-string: "npm:^0.30.21" + picocolors: "npm:^1.1.1" + vite-prerender-plugin: "npm:^0.5.8" + zimmerframe: "npm:^1.1.4" + peerDependencies: + "@babel/core": 7.x + vite: 2.x || 3.x || 4.x || 5.x || 6.x || 7.x || 8.x + checksum: 10c0/fb548ffa0f7a90699ddc909b318d70d1f6f255a26193628578ef81517258d63d50d412b9dc87a5a4d9b13c4a62d47e9688aeee575bc8cdeeeefe5145e62ea741 + languageName: node + linkType: hard + +"@prefresh/babel-plugin@npm:^0.5.2": + version: 0.5.3 + resolution: "@prefresh/babel-plugin@npm:0.5.3" + checksum: 10c0/59cb7bde2dac2265f28f18c8fc3799efd75ebb117c7df19c7fea597a03db4af973ee56a21e9fa2588f707a3ef8a90ddc19e95d1d151641a068d98a690b0bc6ea + languageName: node + linkType: hard + +"@prefresh/core@npm:^1.5.0": + version: 1.5.10 + resolution: "@prefresh/core@npm:1.5.10" + peerDependencies: + preact: ^10.0.0 || ^11.0.0-0 + checksum: 10c0/61320c197b8075f0a2895b2cde75d9600b471b4241eb4a5855547fdef20298f732480e27522bb6e9ca64d47d645dd45717d9c0571446523c5b0701063c486c60 + languageName: node + linkType: hard + +"@prefresh/utils@npm:^1.2.0": + version: 1.2.1 + resolution: "@prefresh/utils@npm:1.2.1" + checksum: 10c0/d36bcc8995128ee9028e2c48cb7812c7736a0c8e228ded586de4595f587b239ec9daac2c82221a476ffd341412dbb62082d09f7191d3ee7f5d0b8a99a45f01a8 + languageName: node + linkType: hard + +"@prefresh/vite@npm:^2.4.11": + version: 2.4.12 + resolution: "@prefresh/vite@npm:2.4.12" + dependencies: + "@babel/core": "npm:^7.22.1" + "@prefresh/babel-plugin": "npm:^0.5.2" + "@prefresh/core": "npm:^1.5.0" + "@prefresh/utils": "npm:^1.2.0" + "@rollup/pluginutils": "npm:^4.2.1" + peerDependencies: + preact: ^10.4.0 || ^11.0.0-0 + vite: ">=2.0.0" + checksum: 10c0/b3e9957a6821831c294457fd0b5063fa83828fc9837b01472a377c858d3335a3c88920e99ce77381de3a63f8394459423d812e973d9afd32950b4e0db66970df + languageName: node + linkType: hard + +"@rollup/pluginutils@npm:^4.2.1": + version: 4.2.1 + resolution: "@rollup/pluginutils@npm:4.2.1" + dependencies: + estree-walker: "npm:^2.0.1" + picomatch: "npm:^2.2.2" + checksum: 10c0/3ee56b2c8f1ed8dfd0a92631da1af3a2dfdd0321948f089b3752b4de1b54dc5076701eadd0e5fc18bd191b77af594ac1db6279e83951238ba16bf8a414c64c48 + languageName: node + linkType: hard + +"@rollup/pluginutils@npm:^5.0.0": + version: 5.4.0 + resolution: "@rollup/pluginutils@npm:5.4.0" + dependencies: + "@types/estree": "npm:^1.0.0" + estree-walker: "npm:^2.0.2" + picomatch: "npm:^4.0.2" + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: 10c0/ccc2cbd3a05df642df60ab05ffb81b2e564bd945e2a118bb8a474ea75b941033c8f44273133d4865643cca1492d0c80b14de1281f74779a64285a80fc3a194d8 + languageName: node + linkType: hard + +"@rollup/rollup-android-arm-eabi@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.62.2" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-android-arm64@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-android-arm64@npm:4.62.2" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-arm64@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-darwin-arm64@npm:4.62.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-x64@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-darwin-x64@npm:4.62.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-arm64@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.62.2" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-x64@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-freebsd-x64@npm:4.62.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-gnueabihf@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.62.2" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-musleabihf@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.62.2" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.62.2" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-musl@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.62.2" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-gnu@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.62.2" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-musl@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-loong64-musl@npm:4.62.2" + conditions: os=linux & cpu=loong64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-gnu@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.62.2" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-musl@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.62.2" + conditions: os=linux & cpu=ppc64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.62.2" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-musl@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.62.2" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-s390x-gnu@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.62.2" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.62.2" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-musl@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.62.2" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-openbsd-x64@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-openbsd-x64@npm:4.62.2" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-openharmony-arm64@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.62.2" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.62.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-ia32-msvc@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.62.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-gnu@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.62.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-msvc@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.62.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@types/esrecurse@npm:^4.3.1": + version: 4.3.1 + resolution: "@types/esrecurse@npm:4.3.1" + checksum: 10c0/90dad74d5da3ad27606d8e8e757322f33171cfeaa15ad558b615cf71bb2a516492d18f55f4816384685a3eb2412142e732bbae9a4a7cd2cf3deb7572aa4ebe03 + languageName: node + linkType: hard + +"@types/estree@npm:1.0.9, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8": + version: 1.0.9 + resolution: "@types/estree@npm:1.0.9" + checksum: 10c0/3ad3286ca2988cd550dafb8f2ad599c8474868e954fa601a36655bdfefd8039f7c714b8c1c7f2ae219ffbd58bd4660e66fa7479a0120fc02d4777057d4865387 + languageName: node + linkType: hard + +"@types/gensync@npm:^1.0.5": + version: 1.0.5 + resolution: "@types/gensync@npm:1.0.5" + checksum: 10c0/7c37c8fc3b1a49efd8889f635a6204008f1c1cc86a09ac6a96e219cf3fc52cf1ff091c4dddc3160e93261d6e599fbba39f5fb698fbf936518d1c410289cdd640 + languageName: node + linkType: hard + +"@types/jsesc@npm:^2.5.0": + version: 2.5.1 + resolution: "@types/jsesc@npm:2.5.1" + checksum: 10c0/12ba7bf5968aeeb36408269f4b5a39718efc6411fa197cf0f5e967ba36ad7b7d555b78787fc480db43ce63ebe6ab0ffe5fd9f64b1ea3b0d073877f0747491b30 + languageName: node + linkType: hard + +"@types/json-schema@npm:^7.0.15": + version: 7.0.15 + resolution: "@types/json-schema@npm:7.0.15" + checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db + languageName: node + linkType: hard + +"@types/parse-json@npm:^4.0.0": + version: 4.0.2 + resolution: "@types/parse-json@npm:4.0.2" + checksum: 10c0/b1b863ac34a2c2172fbe0807a1ec4d5cb684e48d422d15ec95980b81475fac4fdb3768a8b13eef39130203a7c04340fc167bae057c7ebcafd7dec9fe6c36aeb1 + languageName: node + linkType: hard + +"@types/prop-types@npm:^15.7.15": + version: 15.7.15 + resolution: "@types/prop-types@npm:15.7.15" + checksum: 10c0/b59aad1ad19bf1733cf524fd4e618196c6c7690f48ee70a327eb450a42aab8e8a063fbe59ca0a5701aebe2d92d582292c0fb845ea57474f6a15f6994b0e260b2 + languageName: node + linkType: hard + +"@types/react-transition-group@npm:^4.4.12": + version: 4.4.12 + resolution: "@types/react-transition-group@npm:4.4.12" + peerDependencies: + "@types/react": "*" + checksum: 10c0/0441b8b47c69312c89ec0760ba477ba1a0808a10ceef8dc1c64b1013ed78517332c30f18681b0ec0b53542731f1ed015169fed1d127cc91222638ed955478ec7 + languageName: node + linkType: hard + +"abbrev@npm:^5.0.0": + version: 5.0.0 + resolution: "abbrev@npm:5.0.0" + checksum: 10c0/8e88f5c798ea4562d28c5a3e9ad69e3879890bc5d695d8f2dffb8609be4c890aacc8f80ef4553fdd2c6a62d70c2ce8bc57b38074e383beb7487bdafa9ed42ea5 + languageName: node + linkType: hard + +"acorn-jsx@npm:^5.3.2": + version: 5.3.2 + resolution: "acorn-jsx@npm:5.3.2" + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10c0/4c54868fbef3b8d58927d5e33f0a4de35f59012fe7b12cf9dfbb345fb8f46607709e1c4431be869a23fb63c151033d84c4198fa9f79385cec34fcb1dd53974c1 + languageName: node + linkType: hard + +"acorn@npm:^8.16.0": + version: 8.17.0 + resolution: "acorn@npm:8.17.0" + bin: + acorn: bin/acorn + checksum: 10c0/5dcefea5f8f023b6cc24cbe71fb5a8112b601d36c4fa07d14e4e6ffc2ee47383332c46b36c766d9437725aa6660156eae50efa0c838719823b50d7c327c4ed42 + languageName: node + linkType: hard + +"agent-base@npm:6": + version: 6.0.2 + resolution: "agent-base@npm:6.0.2" + dependencies: + debug: "npm:4" + checksum: 10c0/dc4f757e40b5f3e3d674bc9beb4f1048f4ee83af189bae39be99f57bf1f48dde166a8b0a5342a84b5944ee8e6ed1e5a9d801858f4ad44764e84957122fe46261 + languageName: node + linkType: hard + +"ajv@npm:^6.14.0": + version: 6.15.0 + resolution: "ajv@npm:6.15.0" + dependencies: + fast-deep-equal: "npm:^3.1.1" + fast-json-stable-stringify: "npm:^2.0.0" + json-schema-traverse: "npm:^0.4.1" + uri-js: "npm:^4.2.2" + checksum: 10c0/67966499dd272ecde1c2e467084411132891523d057487587879d39ac04207f4351b7b2324c83198013967fbfa632c1612adc960114a30770fbe07a0773b32c2 + languageName: node + linkType: hard + +"array-buffer-byte-length@npm:^1.0.1, array-buffer-byte-length@npm:^1.0.2": + version: 1.0.2 + resolution: "array-buffer-byte-length@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.3" + is-array-buffer: "npm:^3.0.5" + checksum: 10c0/74e1d2d996941c7a1badda9cabb7caab8c449db9086407cad8a1b71d2604cc8abf105db8ca4e02c04579ec58b7be40279ddb09aea4784832984485499f48432d + languageName: node + linkType: hard + +"array-includes@npm:^3.1.6, array-includes@npm:^3.1.8": + version: 3.1.9 + resolution: "array-includes@npm:3.1.9" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.24.0" + es-object-atoms: "npm:^1.1.1" + get-intrinsic: "npm:^1.3.0" + is-string: "npm:^1.1.1" + math-intrinsics: "npm:^1.1.0" + checksum: 10c0/0235fa69078abeac05ac4250699c44996bc6f774a9cbe45db48674ce6bd142f09b327d31482ff75cf03344db4ea03eae23edb862d59378b484b47ed842574856 + languageName: node + linkType: hard + +"array.prototype.findlast@npm:^1.2.5": + version: 1.2.5 + resolution: "array.prototype.findlast@npm:1.2.5" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10c0/ddc952b829145ab45411b9d6adcb51a8c17c76bf89c9dd64b52d5dffa65d033da8c076ed2e17091779e83bc892b9848188d7b4b33453c5565e65a92863cb2775 + languageName: node + linkType: hard + +"array.prototype.flat@npm:^1.3.1": + version: 1.3.3 + resolution: "array.prototype.flat@npm:1.3.3" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10c0/d90e04dfbc43bb96b3d2248576753d1fb2298d2d972e29ca7ad5ec621f0d9e16ff8074dae647eac4f31f4fb7d3f561a7ac005fb01a71f51705a13b5af06a7d8a + languageName: node + linkType: hard + +"array.prototype.flatmap@npm:^1.3.3": + version: 1.3.3 + resolution: "array.prototype.flatmap@npm:1.3.3" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10c0/ba899ea22b9dc9bf276e773e98ac84638ed5e0236de06f13d63a90b18ca9e0ec7c97d622d899796e3773930b946cd2413d098656c0c5d8cc58c6f25c21e6bd54 + languageName: node + linkType: hard + +"array.prototype.tosorted@npm:^1.1.4": + version: 1.1.4 + resolution: "array.prototype.tosorted@npm:1.1.4" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.3" + es-errors: "npm:^1.3.0" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10c0/eb3c4c4fc0381b0bf6dba2ea4d48d367c2827a0d4236a5718d97caaccc6b78f11f4cadf090736e86301d295a6aa4967ed45568f92ced51be8cbbacd9ca410943 + languageName: node + linkType: hard + +"arraybuffer.prototype.slice@npm:^1.0.4": + version: 1.0.4 + resolution: "arraybuffer.prototype.slice@npm:1.0.4" + dependencies: + array-buffer-byte-length: "npm:^1.0.1" + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + is-array-buffer: "npm:^3.0.4" + checksum: 10c0/2f2459caa06ae0f7f615003f9104b01f6435cc803e11bd2a655107d52a1781dc040532dc44d93026b694cc18793993246237423e13a5337e86b43ed604932c06 + languageName: node + linkType: hard + +"ast-metadata-inferer@npm:^0.8.1": + version: 0.8.1 + resolution: "ast-metadata-inferer@npm:0.8.1" + dependencies: + "@mdn/browser-compat-data": "npm:^5.6.19" + checksum: 10c0/4bfa6c268951f31123c2ea902d13d8fda5a226679a028eab7ccc063b5cc10964b07a30f075c3876fadaec315df9a894490ed24e6cb5c185afd373da42fff7a4f + languageName: node + linkType: hard + +"async-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-function@npm:1.0.0" + checksum: 10c0/669a32c2cb7e45091330c680e92eaeb791bc1d4132d827591e499cd1f776ff5a873e77e5f92d0ce795a8d60f10761dec9ddfe7225a5de680f5d357f67b1aac73 + languageName: node + linkType: hard + +"asynckit@npm:^0.4.0": + version: 0.4.0 + resolution: "asynckit@npm:0.4.0" + checksum: 10c0/d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d + languageName: node + linkType: hard + +"available-typed-arrays@npm:^1.0.7": + version: 1.0.7 + resolution: "available-typed-arrays@npm:1.0.7" + dependencies: + possible-typed-array-names: "npm:^1.0.0" + checksum: 10c0/d07226ef4f87daa01bd0fe80f8f310982e345f372926da2e5296aecc25c41cab440916bbaa4c5e1034b453af3392f67df5961124e4b586df1e99793a1374bdb2 + languageName: node + linkType: hard + +"axios@npm:^1.18.1": + version: 1.18.1 + resolution: "axios@npm:1.18.1" + dependencies: + follow-redirects: "npm:^1.16.0" + form-data: "npm:^4.0.5" + https-proxy-agent: "npm:^5.0.1" + proxy-from-env: "npm:^2.1.0" + checksum: 10c0/9d9378a3af0d0ad730a52ad9d15ec7201f3926ad6e7e8bbffc5ae21ca2835ad11d1d9598698f5dd9718917486039f55ea1d7dc23d8e44fa827a55cc3262c02fc + languageName: node + linkType: hard + +"babel-plugin-macros@npm:^3.1.0": + version: 3.1.0 + resolution: "babel-plugin-macros@npm:3.1.0" + dependencies: + "@babel/runtime": "npm:^7.12.5" + cosmiconfig: "npm:^7.0.0" + resolve: "npm:^1.19.0" + checksum: 10c0/c6dfb15de96f67871d95bd2e8c58b0c81edc08b9b087dc16755e7157f357dc1090a8dc60ebab955e92587a9101f02eba07e730adc253a1e4cf593ca3ebd3839c + languageName: node + linkType: hard + +"babel-plugin-transform-hook-names@npm:^1.0.2": + version: 1.0.2 + resolution: "babel-plugin-transform-hook-names@npm:1.0.2" + peerDependencies: + "@babel/core": ^7.12.10 + checksum: 10c0/517b85fe0611d742b3fffad5d0e119fcbd29bf69f95c6970b9ede4cb66453c7106a2d3bf048b35255b78a9d6a9565ad37e73b46c0be1fe557e941c792fad79f0 + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee + languageName: node + linkType: hard + +"balanced-match@npm:^4.0.2": + version: 4.0.4 + resolution: "balanced-match@npm:4.0.4" + checksum: 10c0/07e86102a3eb2ee2a6a1a89164f29d0dbaebd28f2ca3f5ca786f36b8b23d9e417eb3be45a4acf754f837be5ac0a2317de90d3fcb7f4f4dc95720a1f36b26a17b + languageName: node + linkType: hard + +"baseline-browser-mapping@npm:^2.10.12": + version: 2.10.38 + resolution: "baseline-browser-mapping@npm:2.10.38" + bin: + baseline-browser-mapping: dist/cli.cjs + checksum: 10c0/44bbea61ec54df6845f5f6f149c99057370081f1041d6e8dbc92835630ce37da9adc102528ebebd37f563342da2a5eea525d24698ced6674cdcfadb2ed613136 + languageName: node + linkType: hard + +"boolbase@npm:^1.0.0": + version: 1.0.0 + resolution: "boolbase@npm:1.0.0" + checksum: 10c0/e4b53deb4f2b85c52be0e21a273f2045c7b6a6ea002b0e139c744cb6f95e9ec044439a52883b0d74dedd1ff3da55ed140cfdddfed7fb0cccbed373de5dce1bcf + languageName: node + linkType: hard + +"brace-expansion@npm:^1.1.7": + version: 1.1.15 + resolution: "brace-expansion@npm:1.1.15" + dependencies: + balanced-match: "npm:^1.0.0" + concat-map: "npm:0.0.1" + checksum: 10c0/648e273f57cfa9ed67d8a77bdb15b408205465d33da9331808ee3c188d8b55674c9cdbf1f320b65bc562e485e1263360ae62ad355e128e0435891f6430e795d7 + languageName: node + linkType: hard + +"brace-expansion@npm:^5.0.5": + version: 5.0.6 + resolution: "brace-expansion@npm:5.0.6" + dependencies: + balanced-match: "npm:^4.0.2" + checksum: 10c0/8c919869b90f61d533b341d3340be5ee4413232ea89b8246cbc2f38eb014f1d8182785c98a006eaf6111d02dc9eeffefdc240d5ac158625b2ed084dccd4bbf9b + languageName: node + linkType: hard + +"browserslist@npm:^4.24.0, browserslist@npm:^4.25.2": + version: 4.28.2 + resolution: "browserslist@npm:4.28.2" + dependencies: + baseline-browser-mapping: "npm:^2.10.12" + caniuse-lite: "npm:^1.0.30001782" + electron-to-chromium: "npm:^1.5.328" + node-releases: "npm:^2.0.36" + update-browserslist-db: "npm:^1.2.3" + bin: + browserslist: cli.js + checksum: 10c0/c0228b6330f785b7fa59d2d360124ec6d9322f96ed9f3ee1f873e33ecc9503a6f0ffc3b71191a28c4ff6e930b753b30043da1c33844a9548f3018d491f09ce60 + languageName: node + linkType: hard + +"call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind-apply-helpers@npm:1.0.2" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + checksum: 10c0/47bd9901d57b857590431243fea704ff18078b16890a6b3e021e12d279bbf211d039155e27d7566b374d49ee1f8189344bac9833dec7a20cdec370506361c938 + languageName: node + linkType: hard + +"call-bind@npm:^1.0.7, call-bind@npm:^1.0.8, call-bind@npm:^1.0.9": + version: 1.0.9 + resolution: "call-bind@npm:1.0.9" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + get-intrinsic: "npm:^1.3.0" + set-function-length: "npm:^1.2.2" + checksum: 10c0/a6621f6da1444481919ce3b4983dff725691e0754d3507ae483ce56e54985f2da7d6f1df512c56dbf28660745cf1ca52553f1fc9aef5557f3ce353ef14fab714 + languageName: node + linkType: hard + +"call-bound@npm:^1.0.2, call-bound@npm:^1.0.3, call-bound@npm:^1.0.4": + version: 1.0.4 + resolution: "call-bound@npm:1.0.4" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + get-intrinsic: "npm:^1.3.0" + checksum: 10c0/f4796a6a0941e71c766aea672f63b72bc61234c4f4964dc6d7606e3664c307e7d77845328a8f3359ce39ddb377fed67318f9ee203dea1d47e46165dcf2917644 + languageName: node + linkType: hard + +"callsites@npm:^3.0.0": + version: 3.1.0 + resolution: "callsites@npm:3.1.0" + checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301 + languageName: node + linkType: hard + +"caniuse-lite@npm:^1.0.30001782": + version: 1.0.30001799 + resolution: "caniuse-lite@npm:1.0.30001799" + checksum: 10c0/f24f9834edc7b60188f368ce44714695c3901bc4acb7f2a977dd9b7b697e39ddc0f9947fad6224a955b789f36a73432cb888d620aa7280d728f582d3bd8927a7 + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 + languageName: node + linkType: hard + +"clsx@npm:^2.1.1": + version: 2.1.1 + resolution: "clsx@npm:2.1.1" + checksum: 10c0/c4c8eb865f8c82baab07e71bfa8897c73454881c4f99d6bc81585aecd7c441746c1399d08363dc096c550cceaf97bd4ce1e8854e1771e9998d9f94c4fe075839 + languageName: node + linkType: hard + +"combined-stream@npm:^1.0.8": + version: 1.0.8 + resolution: "combined-stream@npm:1.0.8" + dependencies: + delayed-stream: "npm:~1.0.0" + checksum: 10c0/0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5 + languageName: node + linkType: hard + +"concat-map@npm:0.0.1": + version: 0.0.1 + resolution: "concat-map@npm:0.0.1" + checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f + languageName: node + linkType: hard + +"convert-source-map@npm:^1.5.0": + version: 1.9.0 + resolution: "convert-source-map@npm:1.9.0" + checksum: 10c0/281da55454bf8126cbc6625385928c43479f2060984180c42f3a86c8b8c12720a24eac260624a7d1e090004028d2dee78602330578ceec1a08e27cb8bb0a8a5b + languageName: node + linkType: hard + +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b + languageName: node + linkType: hard + +"cosmiconfig@npm:^7.0.0": + version: 7.1.0 + resolution: "cosmiconfig@npm:7.1.0" + dependencies: + "@types/parse-json": "npm:^4.0.0" + import-fresh: "npm:^3.2.1" + parse-json: "npm:^5.0.0" + path-type: "npm:^4.0.0" + yaml: "npm:^1.10.0" + checksum: 10c0/b923ff6af581638128e5f074a5450ba12c0300b71302398ea38dbeabd33bbcaa0245ca9adbedfcf284a07da50f99ede5658c80bb3e39e2ce770a99d28a21ef03 + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.6": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 + languageName: node + linkType: hard + +"css-select@npm:^5.1.0": + version: 5.2.2 + resolution: "css-select@npm:5.2.2" + dependencies: + boolbase: "npm:^1.0.0" + css-what: "npm:^6.1.0" + domhandler: "npm:^5.0.2" + domutils: "npm:^3.0.1" + nth-check: "npm:^2.0.1" + checksum: 10c0/d79fffa97106007f2802589f3ed17b8c903f1c961c0fc28aa8a051eee0cbad394d8446223862efd4c1b40445a6034f626bb639cf2035b0bfc468544177593c99 + languageName: node + linkType: hard + +"css-what@npm:^6.1.0": + version: 6.2.2 + resolution: "css-what@npm:6.2.2" + checksum: 10c0/91e24c26fb977b4ccef30d7007d2668c1c10ac0154cc3f42f7304410e9594fb772aea4f30c832d2993b132ca8d99338050866476210316345ec2e7d47b248a56 + languageName: node + linkType: hard + +"csstype@npm:^3.0.2, csstype@npm:^3.2.3": + version: 3.2.3 + resolution: "csstype@npm:3.2.3" + checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce + languageName: node + linkType: hard + +"data-view-buffer@npm:^1.0.2": + version: 1.0.2 + resolution: "data-view-buffer@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.2" + checksum: 10c0/7986d40fc7979e9e6241f85db8d17060dd9a71bd53c894fa29d126061715e322a4cd47a00b0b8c710394854183d4120462b980b8554012acc1c0fa49df7ad38c + languageName: node + linkType: hard + +"data-view-byte-length@npm:^1.0.2": + version: 1.0.2 + resolution: "data-view-byte-length@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.2" + checksum: 10c0/f8a4534b5c69384d95ac18137d381f18a5cfae1f0fc1df0ef6feef51ef0d568606d970b69e02ea186c6c0f0eac77fe4e6ad96fec2569cc86c3afcc7475068c55 + languageName: node + linkType: hard + +"data-view-byte-offset@npm:^1.0.1": + version: 1.0.1 + resolution: "data-view-byte-offset@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.1" + checksum: 10c0/fa7aa40078025b7810dcffc16df02c480573b7b53ef1205aa6a61533011005c1890e5ba17018c692ce7c900212b547262d33279fde801ad9843edc0863bf78c4 + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.4.3": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6 + languageName: node + linkType: hard + +"deep-is@npm:^0.1.3": + version: 0.1.4 + resolution: "deep-is@npm:0.1.4" + checksum: 10c0/7f0ee496e0dff14a573dc6127f14c95061b448b87b995fc96c017ce0a1e66af1675e73f1d6064407975bc4ea6ab679497a29fff7b5b9c4e99cb10797c1ad0b4c + languageName: node + linkType: hard + +"define-data-property@npm:^1.0.1, define-data-property@npm:^1.1.4": + version: 1.1.4 + resolution: "define-data-property@npm:1.1.4" + dependencies: + es-define-property: "npm:^1.0.0" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.0.1" + checksum: 10c0/dea0606d1483eb9db8d930d4eac62ca0fa16738b0b3e07046cddfacf7d8c868bbe13fa0cb263eb91c7d0d527960dc3f2f2471a69ed7816210307f6744fe62e37 + languageName: node + linkType: hard + +"define-properties@npm:^1.1.3, define-properties@npm:^1.2.1": + version: 1.2.1 + resolution: "define-properties@npm:1.2.1" + dependencies: + define-data-property: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.0" + object-keys: "npm:^1.1.1" + checksum: 10c0/88a152319ffe1396ccc6ded510a3896e77efac7a1bfbaa174a7b00414a1747377e0bb525d303794a47cf30e805c2ec84e575758512c6e44a993076d29fd4e6c3 + languageName: node + linkType: hard + +"delayed-stream@npm:~1.0.0": + version: 1.0.0 + resolution: "delayed-stream@npm:1.0.0" + checksum: 10c0/d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19 + languageName: node + linkType: hard + +"doctrine@npm:^2.1.0": + version: 2.1.0 + resolution: "doctrine@npm:2.1.0" + dependencies: + esutils: "npm:^2.0.2" + checksum: 10c0/b6416aaff1f380bf56c3b552f31fdf7a69b45689368deca72d28636f41c16bb28ec3ebc40ace97db4c1afc0ceeb8120e8492fe0046841c94c2933b2e30a7d5ac + languageName: node + linkType: hard + +"dom-helpers@npm:^5.0.1": + version: 5.2.1 + resolution: "dom-helpers@npm:5.2.1" + dependencies: + "@babel/runtime": "npm:^7.8.7" + csstype: "npm:^3.0.2" + checksum: 10c0/f735074d66dd759b36b158fa26e9d00c9388ee0e8c9b16af941c38f014a37fc80782de83afefd621681b19ac0501034b4f1c4a3bff5caa1b8667f0212b5e124c + languageName: node + linkType: hard + +"dom-serializer@npm:^2.0.0": + version: 2.0.0 + resolution: "dom-serializer@npm:2.0.0" + dependencies: + domelementtype: "npm:^2.3.0" + domhandler: "npm:^5.0.2" + entities: "npm:^4.2.0" + checksum: 10c0/d5ae2b7110ca3746b3643d3ef60ef823f5f078667baf530cec096433f1627ec4b6fa8c072f09d079d7cda915fd2c7bc1b7b935681e9b09e591e1e15f4040b8e2 + languageName: node + linkType: hard + +"domelementtype@npm:^2.3.0": + version: 2.3.0 + resolution: "domelementtype@npm:2.3.0" + checksum: 10c0/686f5a9ef0fff078c1412c05db73a0dce096190036f33e400a07e2a4518e9f56b1e324f5c576a0a747ef0e75b5d985c040b0d51945ce780c0dd3c625a18cd8c9 + languageName: node + linkType: hard + +"domhandler@npm:^5.0.2, domhandler@npm:^5.0.3": + version: 5.0.3 + resolution: "domhandler@npm:5.0.3" + dependencies: + domelementtype: "npm:^2.3.0" + checksum: 10c0/bba1e5932b3e196ad6862286d76adc89a0dbf0c773e5ced1eb01f9af930c50093a084eff14b8de5ea60b895c56a04d5de8bbc4930c5543d029091916770b2d2a + languageName: node + linkType: hard + +"domutils@npm:^3.0.1": + version: 3.2.2 + resolution: "domutils@npm:3.2.2" + dependencies: + dom-serializer: "npm:^2.0.0" + domelementtype: "npm:^2.3.0" + domhandler: "npm:^5.0.3" + checksum: 10c0/47938f473b987ea71cd59e59626eb8666d3aa8feba5266e45527f3b636c7883cca7e582d901531961f742c519d7514636b7973353b648762b2e3bedbf235fada + languageName: node + linkType: hard + +"dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.2.0" + checksum: 10c0/199f2a0c1c16593ca0a145dbf76a962f8033ce3129f01284d48c45ed4e14fea9bbacd7b3610b6cdc33486cef20385ac054948fefc6272fcce645c09468f93031 + languageName: node + linkType: hard + +"electron-to-chromium@npm:^1.5.328": + version: 1.5.376 + resolution: "electron-to-chromium@npm:1.5.376" + checksum: 10c0/c57e306ef12ad258e427218f90e14d155dfba68b23699674d7b66e528ce41ff69b46a78f38545ee7690d080d25d3ebb98338282e95d65510e8fa5fd4f7455a86 + languageName: node + linkType: hard + +"empathic@npm:^2.0.1": + version: 2.0.1 + resolution: "empathic@npm:2.0.1" + checksum: 10c0/577f2868bfcad4ffbf911b57c75016125eb8cc8a7d32cf2d3e9fbcb31bfe6e9e6b66d9457ac34ccb2cd38bff353b3af34287899e0360b8c561ff6d4a048aca62 + languageName: node + linkType: hard + +"entities@npm:^4.2.0": + version: 4.5.0 + resolution: "entities@npm:4.5.0" + checksum: 10c0/5b039739f7621f5d1ad996715e53d964035f75ad3b9a4d38c6b3804bb226e282ffeae2443624d8fdd9c47d8e926ae9ac009c54671243f0c3294c26af7cc85250 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"error-ex@npm:^1.3.1": + version: 1.3.4 + resolution: "error-ex@npm:1.3.4" + dependencies: + is-arrayish: "npm:^0.2.1" + checksum: 10c0/b9e34ff4778b8f3b31a8377e1c654456f4c41aeaa3d10a1138c3b7635d8b7b2e03eb2475d46d8ae055c1f180a1063e100bffabf64ea7e7388b37735df5328664 + languageName: node + linkType: hard + +"es-abstract-get@npm:^1.0.0": + version: 1.0.0 + resolution: "es-abstract-get@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.2" + is-callable: "npm:^1.2.7" + object-inspect: "npm:^1.13.4" + checksum: 10c0/f9b4838ae719752207383a6d95a74590f891122bf26b92f5e72eeedbe53771029e4561f1cf75ea19330b71bcf3d4f536fb0c8f7e2b601fe24d284f46e488c7e3 + languageName: node + linkType: hard + +"es-abstract@npm:^1.17.5, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9, es-abstract@npm:^1.24.0, es-abstract@npm:^1.24.2": + version: 1.24.2 + resolution: "es-abstract@npm:1.24.2" + dependencies: + array-buffer-byte-length: "npm:^1.0.2" + arraybuffer.prototype.slice: "npm:^1.0.4" + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + data-view-buffer: "npm:^1.0.2" + data-view-byte-length: "npm:^1.0.2" + data-view-byte-offset: "npm:^1.0.1" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + es-set-tostringtag: "npm:^2.1.0" + es-to-primitive: "npm:^1.3.0" + function.prototype.name: "npm:^1.1.8" + get-intrinsic: "npm:^1.3.0" + get-proto: "npm:^1.0.1" + get-symbol-description: "npm:^1.1.0" + globalthis: "npm:^1.0.4" + gopd: "npm:^1.2.0" + has-property-descriptors: "npm:^1.0.2" + has-proto: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + internal-slot: "npm:^1.1.0" + is-array-buffer: "npm:^3.0.5" + is-callable: "npm:^1.2.7" + is-data-view: "npm:^1.0.2" + is-negative-zero: "npm:^2.0.3" + is-regex: "npm:^1.2.1" + is-set: "npm:^2.0.3" + is-shared-array-buffer: "npm:^1.0.4" + is-string: "npm:^1.1.1" + is-typed-array: "npm:^1.1.15" + is-weakref: "npm:^1.1.1" + math-intrinsics: "npm:^1.1.0" + object-inspect: "npm:^1.13.4" + object-keys: "npm:^1.1.1" + object.assign: "npm:^4.1.7" + own-keys: "npm:^1.0.1" + regexp.prototype.flags: "npm:^1.5.4" + safe-array-concat: "npm:^1.1.3" + safe-push-apply: "npm:^1.0.0" + safe-regex-test: "npm:^1.1.0" + set-proto: "npm:^1.0.0" + stop-iteration-iterator: "npm:^1.1.0" + string.prototype.trim: "npm:^1.2.10" + string.prototype.trimend: "npm:^1.0.9" + string.prototype.trimstart: "npm:^1.0.8" + typed-array-buffer: "npm:^1.0.3" + typed-array-byte-length: "npm:^1.0.3" + typed-array-byte-offset: "npm:^1.0.4" + typed-array-length: "npm:^1.0.7" + unbox-primitive: "npm:^1.1.0" + which-typed-array: "npm:^1.1.19" + checksum: 10c0/67a5bf21ef5c7d775e6f6131a836323900b4d87194cf544394ac68fe31c57fa53828b978af4a4f551ef307f83a2f910a16b6b982760ad3ddc3dc471f98d5fd1b + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.0, es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 10c0/3f54eb49c16c18707949ff25a1456728c883e81259f045003499efba399c08bad00deebf65cccde8c0e07908c1a225c9d472b7107e558f2a48e28d530e34527c + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 + languageName: node + linkType: hard + +"es-iterator-helpers@npm:^1.2.1": + version: 1.3.3 + resolution: "es-iterator-helpers@npm:1.3.3" + dependencies: + call-bind: "npm:^1.0.9" + call-bound: "npm:^1.0.4" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.24.2" + es-errors: "npm:^1.3.0" + es-set-tostringtag: "npm:^2.1.0" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.3.0" + globalthis: "npm:^1.0.4" + gopd: "npm:^1.2.0" + has-property-descriptors: "npm:^1.0.2" + has-proto: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + internal-slot: "npm:^1.1.0" + iterator.prototype: "npm:^1.1.5" + math-intrinsics: "npm:^1.1.0" + checksum: 10c0/14d45ce96c1eeec1dfb76607f4277163c847cc799a417c69346bfd2a9cce04b1d1426e34cb867dbc3266d2ca516084259ff1836cd2da6bc70b3e25e02f3532a9 + languageName: node + linkType: hard + +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1, es-object-atoms@npm:^1.1.2": + version: 1.1.2 + resolution: "es-object-atoms@npm:1.1.2" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 10c0/1772861f094f739d6f41b579cfb9a18579daffeb434552a370a5fbef50a32d22227e27b63fdbb757b7ddd429d1b42fe52ccae7966d9302a2ec221b6f1b41bbc4 + languageName: node + linkType: hard + +"es-set-tostringtag@npm:^2.1.0": + version: 2.1.0 + resolution: "es-set-tostringtag@npm:2.1.0" + dependencies: + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10c0/ef2ca9ce49afe3931cb32e35da4dcb6d86ab02592cfc2ce3e49ced199d9d0bb5085fc7e73e06312213765f5efa47cc1df553a6a5154584b21448e9fb8355b1af + languageName: node + linkType: hard + +"es-shim-unscopables@npm:^1.0.2": + version: 1.1.0 + resolution: "es-shim-unscopables@npm:1.1.0" + dependencies: + hasown: "npm:^2.0.2" + checksum: 10c0/1b9702c8a1823fc3ef39035a4e958802cf294dd21e917397c561d0b3e195f383b978359816b1732d02b255ccf63e1e4815da0065b95db8d7c992037be3bbbcdb + languageName: node + linkType: hard + +"es-to-primitive@npm:^1.3.0": + version: 1.3.1 + resolution: "es-to-primitive@npm:1.3.1" + dependencies: + es-abstract-get: "npm:^1.0.0" + es-errors: "npm:^1.3.0" + is-callable: "npm:^1.2.7" + is-date-object: "npm:^1.1.0" + is-symbol: "npm:^1.1.1" + checksum: 10c0/288ec25e5d08c2718bab9faa87924e11f42f2b0b59854fc241f1fbbedcadf2682ab3a9a9fb4676fa40c483ce563ea63cfd3e33777cbb53833e94d5f60a851cde + languageName: node + linkType: hard + +"esbuild@npm:^0.27.0": + version: 0.27.7 + resolution: "esbuild@npm:0.27.7" + dependencies: + "@esbuild/aix-ppc64": "npm:0.27.7" + "@esbuild/android-arm": "npm:0.27.7" + "@esbuild/android-arm64": "npm:0.27.7" + "@esbuild/android-x64": "npm:0.27.7" + "@esbuild/darwin-arm64": "npm:0.27.7" + "@esbuild/darwin-x64": "npm:0.27.7" + "@esbuild/freebsd-arm64": "npm:0.27.7" + "@esbuild/freebsd-x64": "npm:0.27.7" + "@esbuild/linux-arm": "npm:0.27.7" + "@esbuild/linux-arm64": "npm:0.27.7" + "@esbuild/linux-ia32": "npm:0.27.7" + "@esbuild/linux-loong64": "npm:0.27.7" + "@esbuild/linux-mips64el": "npm:0.27.7" + "@esbuild/linux-ppc64": "npm:0.27.7" + "@esbuild/linux-riscv64": "npm:0.27.7" + "@esbuild/linux-s390x": "npm:0.27.7" + "@esbuild/linux-x64": "npm:0.27.7" + "@esbuild/netbsd-arm64": "npm:0.27.7" + "@esbuild/netbsd-x64": "npm:0.27.7" + "@esbuild/openbsd-arm64": "npm:0.27.7" + "@esbuild/openbsd-x64": "npm:0.27.7" + "@esbuild/openharmony-arm64": "npm:0.27.7" + "@esbuild/sunos-x64": "npm:0.27.7" + "@esbuild/win32-arm64": "npm:0.27.7" + "@esbuild/win32-ia32": "npm:0.27.7" + "@esbuild/win32-x64": "npm:0.27.7" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/ccd51f0555708bc9ff4ec9dc3ac92d3daacd45ecaac949ca8645984c5c323bf8cefe98c2df307418685e0b4ce37f9a3bdbfe8e3651fe632a0059a436195a17d4 + languageName: node + linkType: hard + +"escalade@npm:^3.2.0": + version: 3.2.0 + resolution: "escalade@npm:3.2.0" + checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 + languageName: node + linkType: hard + +"eslint-config-preact@npm:^2.0.0": + version: 2.0.0 + resolution: "eslint-config-preact@npm:2.0.0" + dependencies: + "@babel/core": "npm:^7.13.16" + "@babel/eslint-parser": "npm:^7.27.5" + "@babel/plugin-syntax-class-properties": "npm:^7.12.13" + "@babel/plugin-syntax-jsx": "npm:^7.12.13" + "@eslint/js": "npm:^9.29.0" + eslint-plugin-compat: "npm:^6.0.2" + eslint-plugin-react: "npm:^7.37.5" + eslint-plugin-react-hooks: "npm:^5.2.0" + globals: "npm:^16.2.0" + peerDependencies: + eslint: ^8.57.1 || ^9.0.0 + checksum: 10c0/aebc4b09eb48d8b8b152d76cec56df59840397c497ea7bb0017d4855c94bb5b87fb211cc386acbddacade50011aa745169fff255893c3e8f23fde9981cb00012 + languageName: node + linkType: hard + +"eslint-plugin-compat@npm:^6.0.2": + version: 6.2.1 + resolution: "eslint-plugin-compat@npm:6.2.1" + dependencies: + "@mdn/browser-compat-data": "npm:^6.1.1" + ast-metadata-inferer: "npm:^0.8.1" + browserslist: "npm:^4.25.2" + find-up: "npm:^5.0.0" + globals: "npm:^15.7.0" + lodash.memoize: "npm:^4.1.2" + semver: "npm:^7.6.2" + peerDependencies: + eslint: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + checksum: 10c0/08dc5ea7c46eed9cd2e656bfc32318fb2d3e5ba661bbd1b88633b316e5ab3ab898f7987353645f5b525c6b9ffa81d47b8c7d4f77b24fed9991c206b156d9a1ae + languageName: node + linkType: hard + +"eslint-plugin-react-hooks@npm:^5.2.0": + version: 5.2.0 + resolution: "eslint-plugin-react-hooks@npm:5.2.0" + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + checksum: 10c0/1c8d50fa5984c6dea32470651807d2922cc3934cf3425e78f84a24c2dfd972e7f019bee84aefb27e0cf2c13fea0ac1d4473267727408feeb1c56333ca1489385 + languageName: node + linkType: hard + +"eslint-plugin-react@npm:^7.37.5": + version: 7.37.5 + resolution: "eslint-plugin-react@npm:7.37.5" + dependencies: + array-includes: "npm:^3.1.8" + array.prototype.findlast: "npm:^1.2.5" + array.prototype.flatmap: "npm:^1.3.3" + array.prototype.tosorted: "npm:^1.1.4" + doctrine: "npm:^2.1.0" + es-iterator-helpers: "npm:^1.2.1" + estraverse: "npm:^5.3.0" + hasown: "npm:^2.0.2" + jsx-ast-utils: "npm:^2.4.1 || ^3.0.0" + minimatch: "npm:^3.1.2" + object.entries: "npm:^1.1.9" + object.fromentries: "npm:^2.0.8" + object.values: "npm:^1.2.1" + prop-types: "npm:^15.8.1" + resolve: "npm:^2.0.0-next.5" + semver: "npm:^6.3.1" + string.prototype.matchall: "npm:^4.0.12" + string.prototype.repeat: "npm:^1.0.0" + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + checksum: 10c0/c850bfd556291d4d9234f5ca38db1436924a1013627c8ab1853f77cac73ec19b020e861e6c7b783436a48b6ffcdfba4547598235a37ad4611b6739f65fd8ad57 + languageName: node + linkType: hard + +"eslint-scope@npm:5.1.1": + version: 5.1.1 + resolution: "eslint-scope@npm:5.1.1" + dependencies: + esrecurse: "npm:^4.3.0" + estraverse: "npm:^4.1.1" + checksum: 10c0/d30ef9dc1c1cbdece34db1539a4933fe3f9b14e1ffb27ecc85987902ee663ad7c9473bbd49a9a03195a373741e62e2f807c4938992e019b511993d163450e70a + languageName: node + linkType: hard + +"eslint-scope@npm:^9.1.2": + version: 9.1.2 + resolution: "eslint-scope@npm:9.1.2" + dependencies: + "@types/esrecurse": "npm:^4.3.1" + "@types/estree": "npm:^1.0.8" + esrecurse: "npm:^4.3.0" + estraverse: "npm:^5.2.0" + checksum: 10c0/9fb8bca5a73e5741efb6cec84467027b6cb6f4203ff9b43a938e272c5cd30800bde46a5c20dfd1609f840225f0b62b7673be391b20acadf8658ca9fa4729b3dd + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^2.1.0": + version: 2.1.0 + resolution: "eslint-visitor-keys@npm:2.1.0" + checksum: 10c0/9f0e3a2db751d84067d15977ac4b4472efd6b303e369e6ff241a99feac04da758f46d5add022c33d06b53596038dbae4b4aceb27c7e68b8dfc1055b35e495787 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^3.4.3": + version: 3.4.3 + resolution: "eslint-visitor-keys@npm:3.4.3" + checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^5.0.1": + version: 5.0.1 + resolution: "eslint-visitor-keys@npm:5.0.1" + checksum: 10c0/16190bdf2cbae40a1109384c94450c526a79b0b9c3cb21e544256ed85ac48a4b84db66b74a6561d20fe6ab77447f150d711c2ad5ad74df4fcc133736bce99678 + languageName: node + linkType: hard + +"eslint@npm:^10.5.0": + version: 10.5.0 + resolution: "eslint@npm:10.5.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.8.0" + "@eslint-community/regexpp": "npm:^4.12.2" + "@eslint/config-array": "npm:^0.23.5" + "@eslint/config-helpers": "npm:^0.6.0" + "@eslint/core": "npm:^1.2.1" + "@eslint/plugin-kit": "npm:^0.7.2" + "@humanfs/node": "npm:^0.16.6" + "@humanwhocodes/module-importer": "npm:^1.0.1" + "@humanwhocodes/retry": "npm:^0.4.2" + "@types/estree": "npm:^1.0.6" + ajv: "npm:^6.14.0" + cross-spawn: "npm:^7.0.6" + debug: "npm:^4.3.2" + escape-string-regexp: "npm:^4.0.0" + eslint-scope: "npm:^9.1.2" + eslint-visitor-keys: "npm:^5.0.1" + espree: "npm:^11.2.0" + esquery: "npm:^1.7.0" + esutils: "npm:^2.0.2" + fast-deep-equal: "npm:^3.1.3" + file-entry-cache: "npm:^8.0.0" + find-up: "npm:^5.0.0" + glob-parent: "npm:^6.0.2" + ignore: "npm:^5.2.0" + imurmurhash: "npm:^0.1.4" + is-glob: "npm:^4.0.0" + json-stable-stringify-without-jsonify: "npm:^1.0.1" + minimatch: "npm:^10.2.4" + natural-compare: "npm:^1.4.0" + optionator: "npm:^0.9.3" + peerDependencies: + jiti: "*" + peerDependenciesMeta: + jiti: + optional: true + bin: + eslint: bin/eslint.js + checksum: 10c0/e1f7a1d442027e5478945cb07b6a382adc3f149fbfd3dbc14951a7a9fa312546b27fb35b0556897ca1d04539ad16ee85bda75ad9f724873e1153d1fbca0d6145 + languageName: node + linkType: hard + +"espree@npm:^11.2.0": + version: 11.2.0 + resolution: "espree@npm:11.2.0" + dependencies: + acorn: "npm:^8.16.0" + acorn-jsx: "npm:^5.3.2" + eslint-visitor-keys: "npm:^5.0.1" + checksum: 10c0/cf87e18ffd9dc113eb8d16588e7757701bc10c9934a71cce8b89c2611d51672681a918307bd6b19ac3ccd0e7ba1cbccc2f815b36b52fa7e73097b251014c3d81 + languageName: node + linkType: hard + +"esquery@npm:^1.7.0": + version: 1.7.0 + resolution: "esquery@npm:1.7.0" + dependencies: + estraverse: "npm:^5.1.0" + checksum: 10c0/77d5173db450b66f3bc685d11af4c90cffeedb340f34a39af96d43509a335ce39c894fd79233df32d38f5e4e219fa0f7076f6ec90bae8320170ba082c0db4793 + languageName: node + linkType: hard + +"esrecurse@npm:^4.3.0": + version: 4.3.0 + resolution: "esrecurse@npm:4.3.0" + dependencies: + estraverse: "npm:^5.2.0" + checksum: 10c0/81a37116d1408ded88ada45b9fb16dbd26fba3aadc369ce50fcaf82a0bac12772ebd7b24cd7b91fc66786bf2c1ac7b5f196bc990a473efff972f5cb338877cf5 + languageName: node + linkType: hard + +"estraverse@npm:^4.1.1": + version: 4.3.0 + resolution: "estraverse@npm:4.3.0" + checksum: 10c0/9cb46463ef8a8a4905d3708a652d60122a0c20bb58dec7e0e12ab0e7235123d74214fc0141d743c381813e1b992767e2708194f6f6e0f9fd00c1b4e0887b8b6d + languageName: node + linkType: hard + +"estraverse@npm:^5.1.0, estraverse@npm:^5.2.0, estraverse@npm:^5.3.0": + version: 5.3.0 + resolution: "estraverse@npm:5.3.0" + checksum: 10c0/1ff9447b96263dec95d6d67431c5e0771eb9776427421260a3e2f0fdd5d6bd4f8e37a7338f5ad2880c9f143450c9b1e4fc2069060724570a49cf9cf0312bd107 + languageName: node + linkType: hard + +"estree-walker@npm:^2.0.1, estree-walker@npm:^2.0.2": + version: 2.0.2 + resolution: "estree-walker@npm:2.0.2" + checksum: 10c0/53a6c54e2019b8c914dc395890153ffdc2322781acf4bd7d1a32d7aedc1710807bdcd866ac133903d5629ec601fbb50abe8c2e5553c7f5a0afdd9b6af6c945af + languageName: node + linkType: hard + +"esutils@npm:^2.0.2": + version: 2.0.3 + resolution: "esutils@npm:2.0.3" + checksum: 10c0/9a2fe69a41bfdade834ba7c42de4723c97ec776e40656919c62cbd13607c45e127a003f05f724a1ea55e5029a4cf2de444b13009f2af71271e42d93a637137c7 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.3 + resolution: "exponential-backoff@npm:3.1.3" + checksum: 10c0/77e3ae682b7b1f4972f563c6dbcd2b0d54ac679e62d5d32f3e5085feba20483cf28bd505543f520e287a56d4d55a28d7874299941faf637e779a1aa5994d1267 + languageName: node + linkType: hard + +"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": + version: 3.1.3 + resolution: "fast-deep-equal@npm:3.1.3" + checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 + languageName: node + linkType: hard + +"fast-json-stable-stringify@npm:^2.0.0": + version: 2.1.0 + resolution: "fast-json-stable-stringify@npm:2.1.0" + checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b + languageName: node + linkType: hard + +"fast-levenshtein@npm:^2.0.6": + version: 2.0.6 + resolution: "fast-levenshtein@npm:2.0.6" + checksum: 10c0/111972b37338bcb88f7d9e2c5907862c280ebf4234433b95bc611e518d192ccb2d38119c4ac86e26b668d75f7f3894f4ff5c4982899afced7ca78633b08287c4 + languageName: node + linkType: hard + +"fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f + languageName: node + linkType: hard + +"file-entry-cache@npm:^8.0.0": + version: 8.0.0 + resolution: "file-entry-cache@npm:8.0.0" + dependencies: + flat-cache: "npm:^4.0.0" + checksum: 10c0/9e2b5938b1cd9b6d7e3612bdc533afd4ac17b2fc646569e9a8abbf2eb48e5eb8e316bc38815a3ef6a1b456f4107f0d0f055a614ca613e75db6bf9ff4d72c1638 + languageName: node + linkType: hard + +"find-root@npm:^1.1.0": + version: 1.1.0 + resolution: "find-root@npm:1.1.0" + checksum: 10c0/1abc7f3bf2f8d78ff26d9e00ce9d0f7b32e5ff6d1da2857bcdf4746134c422282b091c672cde0572cac3840713487e0a7a636af9aa1b74cb11894b447a521efa + languageName: node + linkType: hard + +"find-up@npm:^5.0.0": + version: 5.0.0 + resolution: "find-up@npm:5.0.0" + dependencies: + locate-path: "npm:^6.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/062c5a83a9c02f53cdd6d175a37ecf8f87ea5bbff1fdfb828f04bfa021441bc7583e8ebc0872a4c1baab96221fb8a8a275a19809fb93fbc40bd69ec35634069a + languageName: node + linkType: hard + +"flat-cache@npm:^4.0.0": + version: 4.0.1 + resolution: "flat-cache@npm:4.0.1" + dependencies: + flatted: "npm:^3.2.9" + keyv: "npm:^4.5.4" + checksum: 10c0/2c59d93e9faa2523e4fda6b4ada749bed432cfa28c8e251f33b25795e426a1c6dbada777afb1f74fcfff33934fdbdea921ee738fcc33e71adc9d6eca984a1cfc + languageName: node + linkType: hard + +"flatted@npm:^3.2.9": + version: 3.4.2 + resolution: "flatted@npm:3.4.2" + checksum: 10c0/a65b67aae7172d6cdf63691be7de6c5cd5adbdfdfe2e9da1a09b617c9512ed794037741ee53d93114276bff3f93cd3b0d97d54f9b316e1e4885dde6e9ffdf7ed + languageName: node + linkType: hard + +"follow-redirects@npm:^1.16.0": + version: 1.16.0 + resolution: "follow-redirects@npm:1.16.0" + peerDependenciesMeta: + debug: + optional: true + checksum: 10c0/a1e2900163e6f1b4d1ed5c221b607f41decbab65534c63fe7e287e40a5d552a6496e7d9d7d976fa4ba77b4c51c11e5e9f683f10b43011ea11e442ff128d0e181 + languageName: node + linkType: hard + +"for-each@npm:^0.3.3, for-each@npm:^0.3.5": + version: 0.3.5 + resolution: "for-each@npm:0.3.5" + dependencies: + is-callable: "npm:^1.2.7" + checksum: 10c0/0e0b50f6a843a282637d43674d1fb278dda1dd85f4f99b640024cfb10b85058aac0cc781bf689d5fe50b4b7f638e91e548560723a4e76e04fe96ae35ef039cee + languageName: node + linkType: hard + +"form-data@npm:^4.0.5": + version: 4.0.6 + resolution: "form-data@npm:4.0.6" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + es-set-tostringtag: "npm:^2.1.0" + hasown: "npm:^2.0.4" + mime-types: "npm:^2.1.35" + checksum: 10c0/43947a77bf0ff45c6ceed789778982d47a3f3e720a74b71721174ebf3310a5f1a8be1d6b38a3ee3688e8a18a2c4273073ec0844cd37efda3eaf46d41c9c318ff + languageName: node + linkType: hard + +"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 + languageName: node + linkType: hard + +"function.prototype.name@npm:^1.1.6, function.prototype.name@npm:^1.1.8": + version: 1.2.0 + resolution: "function.prototype.name@npm:1.2.0" + dependencies: + call-bind: "npm:^1.0.9" + call-bound: "npm:^1.0.4" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + functions-have-names: "npm:^1.2.3" + has-property-descriptors: "npm:^1.0.2" + hasown: "npm:^2.0.4" + is-callable: "npm:^1.2.7" + is-document.all: "npm:^1.0.0" + checksum: 10c0/b20e6370ef4f7d56d0bedf5719f6684a517a8dd3334209b4d9f51e8834859302a584187156bf024cda9f50ba2479e4d6764ac34af9532ea47d2f4d9fa6bcf90d + languageName: node + linkType: hard + +"functions-have-names@npm:^1.2.3": + version: 1.2.3 + resolution: "functions-have-names@npm:1.2.3" + checksum: 10c0/33e77fd29bddc2d9bb78ab3eb854c165909201f88c75faa8272e35899e2d35a8a642a15e7420ef945e1f64a9670d6aa3ec744106b2aa42be68ca5114025954ca + languageName: node + linkType: hard + +"generator-function@npm:^2.0.0": + version: 2.0.1 + resolution: "generator-function@npm:2.0.1" + checksum: 10c0/8a9f59df0f01cfefafdb3b451b80555e5cf6d76487095db91ac461a0e682e4ff7a9dbce15f4ecec191e53586d59eece01949e05a4b4492879600bbbe8e28d6b8 + languageName: node + linkType: hard + +"gensync@npm:^1.0.0-beta.2": + version: 1.0.0-beta.2 + resolution: "gensync@npm:1.0.0-beta.2" + checksum: 10c0/782aba6cba65b1bb5af3b095d96249d20edbe8df32dbf4696fd49be2583faf676173bf4809386588828e4dd76a3354fcbeb577bab1c833ccd9fc4577f26103f8 + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7, get-intrinsic@npm:^1.3.0": + version: 1.3.0 + resolution: "get-intrinsic@npm:1.3.0" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + function-bind: "npm:^1.1.2" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 10c0/52c81808af9a8130f581e6a6a83e1ba4a9f703359e7a438d1369a5267a25412322f03dcbd7c549edaef0b6214a0630a28511d7df0130c93cfd380f4fa0b5b66a + languageName: node + linkType: hard + +"get-proto@npm:^1.0.0, get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: "npm:^1.0.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/9224acb44603c5526955e83510b9da41baf6ae73f7398875fba50edc5e944223a89c4a72b070fcd78beb5f7bdda58ecb6294adc28f7acfc0da05f76a2399643c + languageName: node + linkType: hard + +"get-symbol-description@npm:^1.1.0": + version: 1.1.0 + resolution: "get-symbol-description@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + checksum: 10c0/d6a7d6afca375779a4b307738c9e80dbf7afc0bdbe5948768d54ab9653c865523d8920e670991a925936eb524b7cb6a6361d199a760b21d0ca7620194455aa4b + languageName: node + linkType: hard + +"glob-parent@npm:^6.0.2": + version: 6.0.2 + resolution: "glob-parent@npm:6.0.2" + dependencies: + is-glob: "npm:^4.0.3" + checksum: 10c0/317034d88654730230b3f43bb7ad4f7c90257a426e872ea0bf157473ac61c99bf5d205fad8f0185f989be8d2fa6d3c7dce1645d99d545b6ea9089c39f838e7f8 + languageName: node + linkType: hard + +"globals@npm:^15.7.0": + version: 15.15.0 + resolution: "globals@npm:15.15.0" + checksum: 10c0/f9ae80996392ca71316495a39bec88ac43ae3525a438b5626cd9d5ce9d5500d0a98a266409605f8cd7241c7acf57c354a48111ea02a767ba4f374b806d6861fe + languageName: node + linkType: hard + +"globals@npm:^16.2.0": + version: 16.5.0 + resolution: "globals@npm:16.5.0" + checksum: 10c0/615241dae7851c8012f5aa0223005b1ed6607713d6813de0741768bd4ddc39353117648f1a7086b4b0fa45eae733f1c0a0fe369aa4e543bb63f8de8990178ea9 + languageName: node + linkType: hard + +"globalthis@npm:^1.0.4": + version: 1.0.4 + resolution: "globalthis@npm:1.0.4" + dependencies: + define-properties: "npm:^1.2.1" + gopd: "npm:^1.0.1" + checksum: 10c0/9d156f313af79d80b1566b93e19285f481c591ad6d0d319b4be5e03750d004dde40a39a0f26f7e635f9007a3600802f53ecd85a759b86f109e80a5f705e01846 + languageName: node + linkType: hard + +"gopd@npm:^1.0.1, gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 10c0/50fff1e04ba2b7737c097358534eacadad1e68d24cccee3272e04e007bed008e68d2614f3987788428fd192a5ae3889d08fb2331417e4fc4a9ab366b2043cead + languageName: node + linkType: hard + +"graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"has-bigints@npm:^1.0.2": + version: 1.1.0 + resolution: "has-bigints@npm:1.1.0" + checksum: 10c0/2de0cdc4a1ccf7a1e75ffede1876994525ac03cc6f5ae7392d3415dd475cd9eee5bceec63669ab61aa997ff6cceebb50ef75561c7002bed8988de2b9d1b40788 + languageName: node + linkType: hard + +"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.2": + version: 1.0.2 + resolution: "has-property-descriptors@npm:1.0.2" + dependencies: + es-define-property: "npm:^1.0.0" + checksum: 10c0/253c1f59e80bb476cf0dde8ff5284505d90c3bdb762983c3514d36414290475fe3fd6f574929d84de2a8eec00d35cf07cb6776205ff32efd7c50719125f00236 + languageName: node + linkType: hard + +"has-proto@npm:^1.2.0": + version: 1.2.0 + resolution: "has-proto@npm:1.2.0" + dependencies: + dunder-proto: "npm:^1.0.0" + checksum: 10c0/46538dddab297ec2f43923c3d35237df45d8c55a6fc1067031e04c13ed8a9a8f94954460632fd4da84c31a1721eefee16d901cbb1ae9602bab93bb6e08f93b95 + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: 10c0/dde0a734b17ae51e84b10986e651c664379018d10b91b6b0e9b293eddb32f0f069688c841fb40f19e9611546130153e0a2a48fd7f512891fb000ddfa36f5a20e + languageName: node + linkType: hard + +"has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" + dependencies: + has-symbols: "npm:^1.0.3" + checksum: 10c0/a8b166462192bafe3d9b6e420a1d581d93dd867adb61be223a17a8d6dad147aa77a8be32c961bb2f27b3ef893cae8d36f564ab651f5e9b7938ae86f74027c48c + languageName: node + linkType: hard + +"hasown@npm:^2.0.2, hasown@npm:^2.0.3, hasown@npm:^2.0.4": + version: 2.0.4 + resolution: "hasown@npm:2.0.4" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/2d8de939e270b70618f8cebb69746620db10617dbb495bc66ddad326955ea24d3ca4af133aff3eb7c1853e0218f867bc2b050ec26fe02e3aea58f880ffc5e506 + languageName: node + linkType: hard + +"he@npm:1.2.0": + version: 1.2.0 + resolution: "he@npm:1.2.0" + bin: + he: bin/he + checksum: 10c0/a27d478befe3c8192f006cdd0639a66798979dfa6e2125c6ac582a19a5ebfec62ad83e8382e6036170d873f46e4536a7e795bf8b95bf7c247f4cc0825ccc8c17 + languageName: node + linkType: hard + +"hoist-non-react-statics@npm:^3.3.1": + version: 3.3.2 + resolution: "hoist-non-react-statics@npm:3.3.2" + dependencies: + react-is: "npm:^16.7.0" + checksum: 10c0/fe0889169e845d738b59b64badf5e55fa3cf20454f9203d1eb088df322d49d4318df774828e789898dcb280e8a5521bb59b3203385662ca5e9218a6ca5820e74 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^5.0.1": + version: 5.0.1 + resolution: "https-proxy-agent@npm:5.0.1" + dependencies: + agent-base: "npm:6" + debug: "npm:4" + checksum: 10c0/6dd639f03434003577c62b27cafdb864784ef19b2de430d8ae2a1d45e31c4fd60719e5637b44db1a88a046934307da7089e03d6089ec3ddacc1189d8de8897d1 + languageName: node + linkType: hard + +"ignore@npm:^5.2.0": + version: 5.3.2 + resolution: "ignore@npm:5.3.2" + checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 + languageName: node + linkType: hard + +"import-fresh@npm:^3.2.1": + version: 3.3.1 + resolution: "import-fresh@npm:3.3.1" + dependencies: + parent-module: "npm:^1.0.0" + resolve-from: "npm:^4.0.0" + checksum: 10c0/bf8cc494872fef783249709385ae883b447e3eb09db0ebd15dcead7d9afe7224dad7bd7591c6b73b0b19b3c0f9640eb8ee884f01cfaf2887ab995b0b36a0cbec + languageName: node + linkType: hard + +"import-meta-resolve@npm:^4.2.0": + version: 4.2.0 + resolution: "import-meta-resolve@npm:4.2.0" + checksum: 10c0/3ee8aeecb61d19b49d2703987f977e9d1c7d4ba47db615a570eaa02fe414f40dfa63f7b953e842cbe8470d26df6371332bfcf21b2fd92b0112f9fea80dde2c4c + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 + languageName: node + linkType: hard + +"internal-slot@npm:^1.1.0": + version: 1.1.0 + resolution: "internal-slot@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + hasown: "npm:^2.0.2" + side-channel: "npm:^1.1.0" + checksum: 10c0/03966f5e259b009a9bf1a78d60da920df198af4318ec004f57b8aef1dd3fe377fbc8cce63a96e8c810010302654de89f9e19de1cd8ad0061d15be28a695465c7 + languageName: node + linkType: hard + +"is-array-buffer@npm:^3.0.4, is-array-buffer@npm:^3.0.5": + version: 3.0.5 + resolution: "is-array-buffer@npm:3.0.5" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + get-intrinsic: "npm:^1.2.6" + checksum: 10c0/c5c9f25606e86dbb12e756694afbbff64bc8b348d1bc989324c037e1068695131930199d6ad381952715dad3a9569333817f0b1a72ce5af7f883ce802e49c83d + languageName: node + linkType: hard + +"is-arrayish@npm:^0.2.1": + version: 0.2.1 + resolution: "is-arrayish@npm:0.2.1" + checksum: 10c0/e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729 + languageName: node + linkType: hard + +"is-async-function@npm:^2.0.0": + version: 2.1.1 + resolution: "is-async-function@npm:2.1.1" + dependencies: + async-function: "npm:^1.0.0" + call-bound: "npm:^1.0.3" + get-proto: "npm:^1.0.1" + has-tostringtag: "npm:^1.0.2" + safe-regex-test: "npm:^1.1.0" + checksum: 10c0/d70c236a5e82de6fc4d44368ffd0c2fee2b088b893511ce21e679da275a5ecc6015ff59a7d7e1bdd7ca39f71a8dbdd253cf8cce5c6b3c91cdd5b42b5ce677298 + languageName: node + linkType: hard + +"is-bigint@npm:^1.1.0": + version: 1.1.0 + resolution: "is-bigint@npm:1.1.0" + dependencies: + has-bigints: "npm:^1.0.2" + checksum: 10c0/f4f4b905ceb195be90a6ea7f34323bf1c18e3793f18922e3e9a73c684c29eeeeff5175605c3a3a74cc38185fe27758f07efba3dbae812e5c5afbc0d2316b40e4 + languageName: node + linkType: hard + +"is-boolean-object@npm:^1.2.1": + version: 1.2.2 + resolution: "is-boolean-object@npm:1.2.2" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/36ff6baf6bd18b3130186990026f5a95c709345c39cd368468e6c1b6ab52201e9fd26d8e1f4c066357b4938b0f0401e1a5000e08257787c1a02f3a719457001e + languageName: node + linkType: hard + +"is-callable@npm:^1.2.7": + version: 1.2.7 + resolution: "is-callable@npm:1.2.7" + checksum: 10c0/ceebaeb9d92e8adee604076971dd6000d38d6afc40bb843ea8e45c5579b57671c3f3b50d7f04869618242c6cee08d1b67806a8cb8edaaaf7c0748b3720d6066f + languageName: node + linkType: hard + +"is-core-module@npm:^2.16.1, is-core-module@npm:^2.16.2": + version: 2.16.2 + resolution: "is-core-module@npm:2.16.2" + dependencies: + hasown: "npm:^2.0.3" + checksum: 10c0/14b4258390283709c15476d023ec173e27458d5d014ccdb8ed39d576e551c3fa45498b7c9fe178f1529c4cb2648ddd58852a6a62107a019f6e349529f277518a + languageName: node + linkType: hard + +"is-data-view@npm:^1.0.1, is-data-view@npm:^1.0.2": + version: 1.0.2 + resolution: "is-data-view@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + get-intrinsic: "npm:^1.2.6" + is-typed-array: "npm:^1.1.13" + checksum: 10c0/ef3548a99d7e7f1370ce21006baca6d40c73e9f15c941f89f0049c79714c873d03b02dae1c64b3f861f55163ecc16da06506c5b8a1d4f16650b3d9351c380153 + languageName: node + linkType: hard + +"is-date-object@npm:^1.1.0": + version: 1.1.0 + resolution: "is-date-object@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.2" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/1a4d199c8e9e9cac5128d32e6626fa7805175af9df015620ac0d5d45854ccf348ba494679d872d37301032e35a54fc7978fba1687e8721b2139aea7870cafa2f + languageName: node + linkType: hard + +"is-document.all@npm:^1.0.0": + version: 1.0.0 + resolution: "is-document.all@npm:1.0.0" + dependencies: + call-bound: "npm:^1.0.4" + checksum: 10c0/955c20ed5bf01d49da8243b4c714947a6ff64b6d9ba0e12bdbfa654a3e7c47f72cc01c6cd2905e85512d02bc3a1290edd73857bca8842566ff9dcfb7c3f92dae + languageName: node + linkType: hard + +"is-extglob@npm:^2.1.1": + version: 2.1.1 + resolution: "is-extglob@npm:2.1.1" + checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 + languageName: node + linkType: hard + +"is-finalizationregistry@npm:^1.1.0": + version: 1.1.1 + resolution: "is-finalizationregistry@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + checksum: 10c0/818dff679b64f19e228a8205a1e2d09989a98e98def3a817f889208cfcbf918d321b251aadf2c05918194803ebd2eb01b14fc9d0b2bea53d984f4137bfca5e97 + languageName: node + linkType: hard + +"is-generator-function@npm:^1.0.10": + version: 1.1.2 + resolution: "is-generator-function@npm:1.1.2" + dependencies: + call-bound: "npm:^1.0.4" + generator-function: "npm:^2.0.0" + get-proto: "npm:^1.0.1" + has-tostringtag: "npm:^1.0.2" + safe-regex-test: "npm:^1.1.0" + checksum: 10c0/83da102e89c3e3b71d67b51d47c9f9bc862bceb58f87201727e27f7fa19d1d90b0ab223644ecaee6fc6e3d2d622bb25c966fbdaf87c59158b01ce7c0fe2fa372 + languageName: node + linkType: hard + +"is-glob@npm:^4.0.0, is-glob@npm:^4.0.3": + version: 4.0.3 + resolution: "is-glob@npm:4.0.3" + dependencies: + is-extglob: "npm:^2.1.1" + checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a + languageName: node + linkType: hard + +"is-map@npm:^2.0.3": + version: 2.0.3 + resolution: "is-map@npm:2.0.3" + checksum: 10c0/2c4d431b74e00fdda7162cd8e4b763d6f6f217edf97d4f8538b94b8702b150610e2c64961340015fe8df5b1fcee33ccd2e9b62619c4a8a3a155f8de6d6d355fc + languageName: node + linkType: hard + +"is-negative-zero@npm:^2.0.3": + version: 2.0.3 + resolution: "is-negative-zero@npm:2.0.3" + checksum: 10c0/bcdcf6b8b9714063ffcfa9929c575ac69bfdabb8f4574ff557dfc086df2836cf07e3906f5bbc4f2a5c12f8f3ba56af640c843cdfc74da8caed86c7c7d66fd08e + languageName: node + linkType: hard + +"is-number-object@npm:^1.1.1": + version: 1.1.1 + resolution: "is-number-object@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/97b451b41f25135ff021d85c436ff0100d84a039bb87ffd799cbcdbea81ef30c464ced38258cdd34f080be08fc3b076ca1f472086286d2aa43521d6ec6a79f53 + languageName: node + linkType: hard + +"is-regex@npm:^1.2.1": + version: 1.2.1 + resolution: "is-regex@npm:1.2.1" + dependencies: + call-bound: "npm:^1.0.2" + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10c0/1d3715d2b7889932349241680032e85d0b492cfcb045acb75ffc2c3085e8d561184f1f7e84b6f8321935b4aea39bc9c6ba74ed595b57ce4881a51dfdbc214e04 + languageName: node + linkType: hard + +"is-set@npm:^2.0.3": + version: 2.0.3 + resolution: "is-set@npm:2.0.3" + checksum: 10c0/f73732e13f099b2dc879c2a12341cfc22ccaca8dd504e6edae26484bd5707a35d503fba5b4daad530a9b088ced1ae6c9d8200fd92e09b428fe14ea79ce8080b7 + languageName: node + linkType: hard + +"is-shared-array-buffer@npm:^1.0.4": + version: 1.0.4 + resolution: "is-shared-array-buffer@npm:1.0.4" + dependencies: + call-bound: "npm:^1.0.3" + checksum: 10c0/65158c2feb41ff1edd6bbd6fd8403a69861cf273ff36077982b5d4d68e1d59278c71691216a4a64632bd76d4792d4d1d2553901b6666d84ade13bba5ea7bc7db + languageName: node + linkType: hard + +"is-string@npm:^1.1.1": + version: 1.1.1 + resolution: "is-string@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/2f518b4e47886bb81567faba6ffd0d8a8333cf84336e2e78bf160693972e32ad00fe84b0926491cc598dee576fdc55642c92e62d0cbe96bf36f643b6f956f94d + languageName: node + linkType: hard + +"is-symbol@npm:^1.1.1": + version: 1.1.1 + resolution: "is-symbol@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.2" + has-symbols: "npm:^1.1.0" + safe-regex-test: "npm:^1.1.0" + checksum: 10c0/f08f3e255c12442e833f75a9e2b84b2d4882fdfd920513cf2a4a2324f0a5b076c8fd913778e3ea5d258d5183e9d92c0cd20e04b03ab3df05316b049b2670af1e + languageName: node + linkType: hard + +"is-typed-array@npm:^1.1.13, is-typed-array@npm:^1.1.14, is-typed-array@npm:^1.1.15": + version: 1.1.15 + resolution: "is-typed-array@npm:1.1.15" + dependencies: + which-typed-array: "npm:^1.1.16" + checksum: 10c0/415511da3669e36e002820584e264997ffe277ff136643a3126cc949197e6ca3334d0f12d084e83b1994af2e9c8141275c741cf2b7da5a2ff62dd0cac26f76c4 + languageName: node + linkType: hard + +"is-weakmap@npm:^2.0.2": + version: 2.0.2 + resolution: "is-weakmap@npm:2.0.2" + checksum: 10c0/443c35bb86d5e6cc5929cd9c75a4024bb0fff9586ed50b092f94e700b89c43a33b186b76dbc6d54f3d3d09ece689ab38dcdc1af6a482cbe79c0f2da0a17f1299 + languageName: node + linkType: hard + +"is-weakref@npm:^1.0.2, is-weakref@npm:^1.1.1": + version: 1.1.1 + resolution: "is-weakref@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + checksum: 10c0/8e0a9c07b0c780949a100e2cab2b5560a48ecd4c61726923c1a9b77b6ab0aa0046c9e7fb2206042296817045376dee2c8ab1dabe08c7c3dfbf195b01275a085b + languageName: node + linkType: hard + +"is-weakset@npm:^2.0.3": + version: 2.0.4 + resolution: "is-weakset@npm:2.0.4" + dependencies: + call-bound: "npm:^1.0.3" + get-intrinsic: "npm:^1.2.6" + checksum: 10c0/6491eba08acb8dc9532da23cb226b7d0192ede0b88f16199e592e4769db0a077119c1f5d2283d1e0d16d739115f70046e887e477eb0e66cd90e1bb29f28ba647 + languageName: node + linkType: hard + +"isarray@npm:^2.0.5": + version: 2.0.5 + resolution: "isarray@npm:2.0.5" + checksum: 10c0/4199f14a7a13da2177c66c31080008b7124331956f47bca57dd0b6ea9f11687aa25e565a2c7a2b519bc86988d10398e3049a1f5df13c9f6b7664154690ae79fd + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d + languageName: node + linkType: hard + +"isexe@npm:^4.0.0": + version: 4.0.0 + resolution: "isexe@npm:4.0.0" + checksum: 10c0/5884815115bceac452877659a9c7726382531592f43dc29e5d48b7c4100661aed54018cb90bd36cb2eaeba521092570769167acbb95c18d39afdccbcca06c5ce + languageName: node + linkType: hard + +"iterator.prototype@npm:^1.1.5": + version: 1.1.5 + resolution: "iterator.prototype@npm:1.1.5" + dependencies: + define-data-property: "npm:^1.1.4" + es-object-atoms: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.6" + get-proto: "npm:^1.0.0" + has-symbols: "npm:^1.1.0" + set-function-name: "npm:^2.0.2" + checksum: 10c0/f7a262808e1b41049ab55f1e9c29af7ec1025a000d243b83edf34ce2416eedd56079b117fa59376bb4a724110690f13aa8427f2ee29a09eec63a7e72367626d0 + languageName: node + linkType: hard + +"js-tokens@npm:^10.0.0": + version: 10.0.0 + resolution: "js-tokens@npm:10.0.0" + checksum: 10c0/a93498747812ba3e0c8626f95f75ab29319f2a13613a0de9e610700405760931624433a0de59eb7c27ff8836e526768fb20783861b86ef89be96676f2c996b64 + languageName: node + linkType: hard + +"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed + languageName: node + linkType: hard + +"jsesc@npm:^3.0.2": + version: 3.1.0 + resolution: "jsesc@npm:3.1.0" + bin: + jsesc: bin/jsesc + checksum: 10c0/531779df5ec94f47e462da26b4cbf05eb88a83d9f08aac2ba04206508fc598527a153d08bd462bae82fc78b3eaa1a908e1a4a79f886e9238641c4cdefaf118b1 + languageName: node + linkType: hard + +"json-buffer@npm:3.0.1": + version: 3.0.1 + resolution: "json-buffer@npm:3.0.1" + checksum: 10c0/0d1c91569d9588e7eef2b49b59851f297f3ab93c7b35c7c221e288099322be6b562767d11e4821da500f3219542b9afd2e54c5dc573107c1126ed1080f8e96d7 + languageName: node + linkType: hard + +"json-parse-even-better-errors@npm:^2.3.0": + version: 2.3.1 + resolution: "json-parse-even-better-errors@npm:2.3.1" + checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3 + languageName: node + linkType: hard + +"json-schema-traverse@npm:^0.4.1": + version: 0.4.1 + resolution: "json-schema-traverse@npm:0.4.1" + checksum: 10c0/108fa90d4cc6f08243aedc6da16c408daf81793bf903e9fd5ab21983cda433d5d2da49e40711da016289465ec2e62e0324dcdfbc06275a607fe3233fde4942ce + languageName: node + linkType: hard + +"json-stable-stringify-without-jsonify@npm:^1.0.1": + version: 1.0.1 + resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" + checksum: 10c0/cb168b61fd4de83e58d09aaa6425ef71001bae30d260e2c57e7d09a5fd82223e2f22a042dedaab8db23b7d9ae46854b08bb1f91675a8be11c5cffebef5fb66a5 + languageName: node + linkType: hard + +"json5@npm:^2.2.3": + version: 2.2.3 + resolution: "json5@npm:2.2.3" + bin: + json5: lib/cli.js + checksum: 10c0/5a04eed94810fa55c5ea138b2f7a5c12b97c3750bc63d11e511dcecbfef758003861522a070c2272764ee0f4e3e323862f386945aeb5b85b87ee43f084ba586c + languageName: node + linkType: hard + +"jsx-ast-utils@npm:^2.4.1 || ^3.0.0": + version: 3.3.5 + resolution: "jsx-ast-utils@npm:3.3.5" + dependencies: + array-includes: "npm:^3.1.6" + array.prototype.flat: "npm:^1.3.1" + object.assign: "npm:^4.1.4" + object.values: "npm:^1.1.6" + checksum: 10c0/a32679e9cb55469cb6d8bbc863f7d631b2c98b7fc7bf172629261751a6e7bc8da6ae374ddb74d5fbd8b06cf0eb4572287b259813d92b36e384024ed35e4c13e1 + languageName: node + linkType: hard + +"keyv@npm:^4.5.4": + version: 4.5.4 + resolution: "keyv@npm:4.5.4" + dependencies: + json-buffer: "npm:3.0.1" + checksum: 10c0/aa52f3c5e18e16bb6324876bb8b59dd02acf782a4b789c7b2ae21107fab95fab3890ed448d4f8dba80ce05391eeac4bfabb4f02a20221342982f806fa2cf271e + languageName: node + linkType: hard + +"kolorist@npm:^1.6.0, kolorist@npm:^1.8.0": + version: 1.8.0 + resolution: "kolorist@npm:1.8.0" + checksum: 10c0/73075db44a692bf6c34a649f3b4b3aea4993b84f6b754cbf7a8577e7c7db44c0bad87752bd23b0ce533f49de2244ce2ce03b7b1b667a85ae170a94782cc50f9b + languageName: node + linkType: hard + +"levn@npm:^0.4.1": + version: 0.4.1 + resolution: "levn@npm:0.4.1" + dependencies: + prelude-ls: "npm:^1.2.1" + type-check: "npm:~0.4.0" + checksum: 10c0/effb03cad7c89dfa5bd4f6989364bfc79994c2042ec5966cb9b95990e2edee5cd8969ddf42616a0373ac49fac1403437deaf6e9050fbbaa3546093a59b9ac94e + languageName: node + linkType: hard + +"lines-and-columns@npm:^1.1.6": + version: 1.2.4 + resolution: "lines-and-columns@npm:1.2.4" + checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d + languageName: node + linkType: hard + +"locate-path@npm:^6.0.0": + version: 6.0.0 + resolution: "locate-path@npm:6.0.0" + dependencies: + p-locate: "npm:^5.0.0" + checksum: 10c0/d3972ab70dfe58ce620e64265f90162d247e87159b6126b01314dd67be43d50e96a50b517bce2d9452a79409c7614054c277b5232377de50416564a77ac7aad3 + languageName: node + linkType: hard + +"lodash.memoize@npm:^4.1.2": + version: 4.1.2 + resolution: "lodash.memoize@npm:4.1.2" + checksum: 10c0/c8713e51eccc650422716a14cece1809cfe34bc5ab5e242b7f8b4e2241c2483697b971a604252807689b9dd69bfe3a98852e19a5b89d506b000b4187a1285df8 + languageName: node + linkType: hard + +"loose-envify@npm:^1.4.0": + version: 1.4.0 + resolution: "loose-envify@npm:1.4.0" + dependencies: + js-tokens: "npm:^3.0.0 || ^4.0.0" + bin: + loose-envify: cli.js + checksum: 10c0/655d110220983c1a4b9c0c679a2e8016d4b67f6e9c7b5435ff5979ecdb20d0813f4dec0a08674fcbdd4846a3f07edbb50a36811fd37930b94aaa0d9daceb017e + languageName: node + linkType: hard + +"lru-cache@npm:^11.0.0": + version: 11.5.1 + resolution: "lru-cache@npm:11.5.1" + checksum: 10c0/7b341cea79a8efe9c6a6f20c8757a77eca5b25d7ff983ccf4e11e547b81f6787824baa1c84705251dff84ab4ffac85717ac354b9d02e465f86a9f8b166409979 + languageName: node + linkType: hard + +"lru-cache@npm:^5.1.1": + version: 5.1.1 + resolution: "lru-cache@npm:5.1.1" + dependencies: + yallist: "npm:^3.0.2" + checksum: 10c0/89b2ef2ef45f543011e38737b8a8622a2f8998cddf0e5437174ef8f1f70a8b9d14a918ab3e232cb3ba343b7abddffa667f0b59075b2b80e6b4d63c3de6127482 + languageName: node + linkType: hard + +"magic-string@npm:0.x >= 0.26.0, magic-string@npm:^0.30.21": + version: 0.30.21 + resolution: "magic-string@npm:0.30.21" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + checksum: 10c0/299378e38f9a270069fc62358522ddfb44e94244baa0d6a8980ab2a9b2490a1d03b236b447eee309e17eb3bddfa482c61259d47960eb018a904f0ded52780c4a + languageName: node + linkType: hard + +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 10c0/7579ff94e899e2f76ab64491d76cf606274c874d8f2af4a442c016bd85688927fcfca157ba6bf74b08e9439dc010b248ce05b96cc7c126a354c3bae7fcb48b7f + languageName: node + linkType: hard + +"mime-db@npm:1.52.0": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa + languageName: node + linkType: hard + +"mime-types@npm:^2.1.35": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: "npm:1.52.0" + checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 + languageName: node + linkType: hard + +"minimatch@npm:^10.2.4": + version: 10.2.5 + resolution: "minimatch@npm:10.2.5" + dependencies: + brace-expansion: "npm:^5.0.5" + checksum: 10c0/6bb058bd6324104b9ec2f763476a35386d05079c1f5fe4fbf1f324a25237cd4534d6813ecd71f48208f4e635c1221899bef94c3c89f7df55698fe373aaae20fd + languageName: node + linkType: hard + +"minimatch@npm:^3.1.2": + version: 3.1.5 + resolution: "minimatch@npm:3.1.5" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10c0/2ecbdc0d33f07bddb0315a8b5afbcb761307a8778b48f0b312418ccbced99f104a2d17d8aca7573433c70e8ccd1c56823a441897a45e384ea76ef401a26ace70 + languageName: node + linkType: hard + +"minipass@npm:^7.0.4, minipass@npm:^7.1.2": + version: 7.1.3 + resolution: "minipass@npm:7.1.3" + checksum: 10c0/539da88daca16533211ea5a9ee98dc62ff5742f531f54640dd34429e621955e91cc280a91a776026264b7f9f6735947629f920944e9c1558369e8bf22eb33fbb + languageName: node + linkType: hard + +"minizlib@npm:^3.1.0": + version: 3.1.0 + resolution: "minizlib@npm:3.1.0" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10c0/5aad75ab0090b8266069c9aabe582c021ae53eb33c6c691054a13a45db3b4f91a7fb1bd79151e6b4e9e9a86727b522527c0a06ec7d45206b745d54cd3097bcec + languageName: node + linkType: hard + +"ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"nanoid@npm:^3.3.12": + version: 3.3.13 + resolution: "nanoid@npm:3.3.13" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/e913cd6d081e3b646788aa36e686964f2746f15935759425c98344053676b9444efedff4d87915bfb38b1626057e66c788f59ee7746b48c2b7886a4b049ca2ac + languageName: node + linkType: hard + +"natural-compare@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare@npm:1.4.0" + checksum: 10c0/f5f9a7974bfb28a91afafa254b197f0f22c684d4a1731763dda960d2c8e375b36c7d690e0d9dc8fba774c537af14a7e979129bca23d88d052fbeb9466955e447 + languageName: node + linkType: hard + +"node-exports-info@npm:^1.6.0": + version: 1.6.0 + resolution: "node-exports-info@npm:1.6.0" + dependencies: + array.prototype.flatmap: "npm:^1.3.3" + es-errors: "npm:^1.3.0" + object.entries: "npm:^1.1.9" + semver: "npm:^6.3.1" + checksum: 10c0/3613f21c60b047e66f168d3499a6be0060d89fb01ddceaa7032c2fb318aff12e4b9b111449c1a9aeb3b848bfdc1d4b6bc8fab327af692319597d21a1e7063692 + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 13.0.0 + resolution: "node-gyp@npm:13.0.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + graceful-fs: "npm:^4.2.6" + nopt: "npm:^10.0.0" + proc-log: "npm:^7.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.5.4" + tinyglobby: "npm:^0.2.12" + undici: "npm:^6.25.0" + which: "npm:^7.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/e7525c427db2d16aa368b8947187de83083d2a8dda23e3e096a71c22ae637ac5bb8ed7cf6c871f1b9118cd2729dbfee4ff3a4245e2b79226900227b15831b492 + languageName: node + linkType: hard + +"node-html-parser@npm:^6.1.12": + version: 6.1.13 + resolution: "node-html-parser@npm:6.1.13" + dependencies: + css-select: "npm:^5.1.0" + he: "npm:1.2.0" + checksum: 10c0/ca36290507da8ec0fa131a0fd67ba62e300365c3950b5a6058b0e32b71b520ff43a5661b19e98a5c9797dbe3428b08db788b602f1f8aa62f2db5bb66e1d80782 + languageName: node + linkType: hard + +"node-releases@npm:^2.0.36": + version: 2.0.48 + resolution: "node-releases@npm:2.0.48" + checksum: 10c0/17eac8493c0be99130793e87fd67feb6291aecb9f4e473dea3b00f59c002cc8b9c0bdc923227c9aa84968da6b2e4c58e75251981503eb367ec9c742715175a34 + languageName: node + linkType: hard + +"nopt@npm:^10.0.0": + version: 10.0.1 + resolution: "nopt@npm:10.0.1" + dependencies: + abbrev: "npm:^5.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/980d89257f9587f3e1f77877ddbf905d6aa3b738ec33e49a4fa1a059a0dd82eb28063982b150654a7ae9de386f2ead60e56172db7d37cf56de545f7392a2a26a + languageName: node + linkType: hard + +"nth-check@npm:^2.0.1": + version: 2.1.1 + resolution: "nth-check@npm:2.1.1" + dependencies: + boolbase: "npm:^1.0.0" + checksum: 10c0/5fee7ff309727763689cfad844d979aedd2204a817fbaaf0e1603794a7c20db28548d7b024692f953557df6ce4a0ee4ae46cd8ebd9b36cfb300b9226b567c479 + languageName: node + linkType: hard + +"object-assign@npm:^4.1.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 + languageName: node + linkType: hard + +"object-inspect@npm:^1.13.3, object-inspect@npm:^1.13.4": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: 10c0/d7f8711e803b96ea3191c745d6f8056ce1f2496e530e6a19a0e92d89b0fa3c76d910c31f0aa270432db6bd3b2f85500a376a83aaba849a8d518c8845b3211692 + languageName: node + linkType: hard + +"object-keys@npm:^1.1.1": + version: 1.1.1 + resolution: "object-keys@npm:1.1.1" + checksum: 10c0/b11f7ccdbc6d406d1f186cdadb9d54738e347b2692a14439ca5ac70c225fa6db46db809711b78589866d47b25fc3e8dee0b4c722ac751e11180f9380e3d8601d + languageName: node + linkType: hard + +"object.assign@npm:^4.1.4, object.assign@npm:^4.1.7": + version: 4.1.7 + resolution: "object.assign@npm:4.1.7" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + has-symbols: "npm:^1.1.0" + object-keys: "npm:^1.1.1" + checksum: 10c0/3b2732bd860567ea2579d1567525168de925a8d852638612846bd8082b3a1602b7b89b67b09913cbb5b9bd6e95923b2ae73580baa9d99cb4e990564e8cbf5ddc + languageName: node + linkType: hard + +"object.entries@npm:^1.1.9": + version: 1.1.9 + resolution: "object.entries@npm:1.1.9" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.1.1" + checksum: 10c0/d4b8c1e586650407da03370845f029aa14076caca4e4d4afadbc69cfb5b78035fd3ee7be417141abdb0258fa142e59b11923b4c44d8b1255b28f5ffcc50da7db + languageName: node + linkType: hard + +"object.fromentries@npm:^2.0.8": + version: 2.0.8 + resolution: "object.fromentries@npm:2.0.8" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/cd4327e6c3369cfa805deb4cbbe919bfb7d3aeebf0bcaba291bb568ea7169f8f8cdbcabe2f00b40db0c20cd20f08e11b5f3a5a36fb7dd3fe04850c50db3bf83b + languageName: node + linkType: hard + +"object.values@npm:^1.1.6, object.values@npm:^1.2.1": + version: 1.2.1 + resolution: "object.values@npm:1.2.1" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/3c47814fdc64842ae3d5a74bc9d06bdd8d21563c04d9939bf6716a9c00596a4ebc342552f8934013d1ec991c74e3671b26710a0c51815f0b603795605ab6b2c9 + languageName: node + linkType: hard + +"obug@npm:^2.1.1": + version: 2.1.3 + resolution: "obug@npm:2.1.3" + checksum: 10c0/cb8187fed0a5fc8445507c950e89f3c1bd43895658c398b5803f6b7804dfa0c562975ecce1e67f3d9247d521452a5bfade9e0e951cc0326b7444272f7c24d25f + languageName: node + linkType: hard + +"optionator@npm:^0.9.3": + version: 0.9.4 + resolution: "optionator@npm:0.9.4" + dependencies: + deep-is: "npm:^0.1.3" + fast-levenshtein: "npm:^2.0.6" + levn: "npm:^0.4.1" + prelude-ls: "npm:^1.2.1" + type-check: "npm:^0.4.0" + word-wrap: "npm:^1.2.5" + checksum: 10c0/4afb687a059ee65b61df74dfe87d8d6815cd6883cb8b3d5883a910df72d0f5d029821f37025e4bccf4048873dbdb09acc6d303d27b8f76b1a80dd5a7d5334675 + languageName: node + linkType: hard + +"own-keys@npm:^1.0.1": + version: 1.0.1 + resolution: "own-keys@npm:1.0.1" + dependencies: + get-intrinsic: "npm:^1.2.6" + object-keys: "npm:^1.1.1" + safe-push-apply: "npm:^1.0.0" + checksum: 10c0/6dfeb3455bff92ec3f16a982d4e3e65676345f6902d9f5ded1d8265a6318d0200ce461956d6d1c70053c7fe9f9fe65e552faac03f8140d37ef0fdd108e67013a + languageName: node + linkType: hard + +"p-limit@npm:^3.0.2": + version: 3.1.0 + resolution: "p-limit@npm:3.1.0" + dependencies: + yocto-queue: "npm:^0.1.0" + checksum: 10c0/9db675949dbdc9c3763c89e748d0ef8bdad0afbb24d49ceaf4c46c02c77d30db4e0652ed36d0a0a7a95154335fab810d95c86153105bb73b3a90448e2bb14e1a + languageName: node + linkType: hard + +"p-locate@npm:^5.0.0": + version: 5.0.0 + resolution: "p-locate@npm:5.0.0" + dependencies: + p-limit: "npm:^3.0.2" + checksum: 10c0/2290d627ab7903b8b70d11d384fee714b797f6040d9278932754a6860845c4d3190603a0772a663c8cb5a7b21d1b16acb3a6487ebcafa9773094edc3dfe6009a + languageName: node + linkType: hard + +"parent-module@npm:^1.0.0": + version: 1.0.1 + resolution: "parent-module@npm:1.0.1" + dependencies: + callsites: "npm:^3.0.0" + checksum: 10c0/c63d6e80000d4babd11978e0d3fee386ca7752a02b035fd2435960ffaa7219dc42146f07069fb65e6e8bf1caef89daf9af7535a39bddf354d78bf50d8294f556 + languageName: node + linkType: hard + +"parse-json@npm:^5.0.0": + version: 5.2.0 + resolution: "parse-json@npm:5.2.0" + dependencies: + "@babel/code-frame": "npm:^7.0.0" + error-ex: "npm:^1.3.1" + json-parse-even-better-errors: "npm:^2.3.0" + lines-and-columns: "npm:^1.1.6" + checksum: 10c0/77947f2253005be7a12d858aedbafa09c9ae39eb4863adf330f7b416ca4f4a08132e453e08de2db46459256fb66afaac5ee758b44fe6541b7cdaf9d252e59585 + languageName: node + linkType: hard + +"path-exists@npm:^4.0.0": + version: 4.0.0 + resolution: "path-exists@npm:4.0.0" + checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b + languageName: node + linkType: hard + +"path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c + languageName: node + linkType: hard + +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 + languageName: node + linkType: hard + +"path-type@npm:^4.0.0": + version: 4.0.0 + resolution: "path-type@npm:4.0.0" + checksum: 10c0/666f6973f332f27581371efaf303fd6c272cc43c2057b37aa99e3643158c7e4b2626549555d88626e99ea9e046f82f32e41bbde5f1508547e9a11b149b52387c + languageName: node + linkType: hard + +"picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 + languageName: node + linkType: hard + +"picomatch@npm:^2.2.2": + version: 2.3.2 + resolution: "picomatch@npm:2.3.2" + checksum: 10c0/a554d1709e59be97d1acb9eaedbbc700a5c03dbd4579807baed95100b00420bc729335440ef15004ae2378984e2487a7c1cebd743cfdb72b6fa9ab69223c0d61 + languageName: node + linkType: hard + +"picomatch@npm:^4.0.2, picomatch@npm:^4.0.3, picomatch@npm:^4.0.4": + version: 4.0.4 + resolution: "picomatch@npm:4.0.4" + checksum: 10c0/e2c6023372cc7b5764719a5ffb9da0f8e781212fa7ca4bd0562db929df8e117460f00dff3cb7509dacfc06b86de924b247f504d0ce1806a37fac4633081466b0 + languageName: node + linkType: hard + +"possible-typed-array-names@npm:^1.0.0, possible-typed-array-names@npm:^1.1.0": + version: 1.1.0 + resolution: "possible-typed-array-names@npm:1.1.0" + checksum: 10c0/c810983414142071da1d644662ce4caebce890203eb2bc7bf119f37f3fe5796226e117e6cca146b521921fa6531072674174a3325066ac66fce089a53e1e5196 + languageName: node + linkType: hard + +"postcss@npm:^8.5.6": + version: 8.5.15 + resolution: "postcss@npm:8.5.15" + dependencies: + nanoid: "npm:^3.3.12" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/7f2e63ae22fbe43aace1bf652bd99da4e90737c64194d49e51ddc9cd0f9e51ff2861a7d734379b494deffa03a880a5c65eec70bc29ee9ebaa7136dde3eee8f31 + languageName: node + linkType: hard + +"preact@npm:^10.26.9": + version: 10.29.2 + resolution: "preact@npm:10.29.2" + checksum: 10c0/2ee161274e475804524608d42bba8c79920636b322293248796a9e5bf4c3332336f8c0eca1b1566ed09cdee95e408eb20b5b7613d95820ea6b6eb8fc090ef60a + languageName: node + linkType: hard + +"prelude-ls@npm:^1.2.1": + version: 1.2.1 + resolution: "prelude-ls@npm:1.2.1" + checksum: 10c0/b00d617431e7886c520a6f498a2e14c75ec58f6d93ba48c3b639cf241b54232d90daa05d83a9e9b9fef6baa63cb7e1e4602c2372fea5bc169668401eb127d0cd + languageName: node + linkType: hard + +"proc-log@npm:^7.0.0": + version: 7.0.0 + resolution: "proc-log@npm:7.0.0" + checksum: 10c0/b89c2d862604f35fec795477b0c7e376feab3ba0d4f4d291c4e959567442697cf451ac557d0623c1cc38af45a78128b983410f397a10c5d3a67f76c33de4754b + languageName: node + linkType: hard + +"prop-types@npm:^15.6.2, prop-types@npm:^15.8.1": + version: 15.8.1 + resolution: "prop-types@npm:15.8.1" + dependencies: + loose-envify: "npm:^1.4.0" + object-assign: "npm:^4.1.1" + react-is: "npm:^16.13.1" + checksum: 10c0/59ece7ca2fb9838031d73a48d4becb9a7cc1ed10e610517c7d8f19a1e02fa47f7c27d557d8a5702bec3cfeccddc853579832b43f449e54635803f277b1c78077 + languageName: node + linkType: hard + +"proxy-from-env@npm:^2.1.0": + version: 2.1.0 + resolution: "proxy-from-env@npm:2.1.0" + checksum: 10c0/ed01729fd4d094eab619cd7e17ce3698b3413b31eb102c4904f9875e677cd207392795d5b4adee9cec359dfd31c44d5ad7595a3a3ad51c40250e141512281c58 + languageName: node + linkType: hard + +"punycode@npm:^2.1.0": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 + languageName: node + linkType: hard + +"react-is@npm:^16.13.1, react-is@npm:^16.7.0": + version: 16.13.1 + resolution: "react-is@npm:16.13.1" + checksum: 10c0/33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1 + languageName: node + linkType: hard + +"react-is@npm:^19.2.6": + version: 19.2.7 + resolution: "react-is@npm:19.2.7" + checksum: 10c0/419fe54d5bd7fdf5414a5bb7bd9a1e0e36f9fae28ffb4cb73290fbe342bde15d8584a90d1db62547f6aa03018dce517b178a041abb522136cd4b4b51b4e94c83 + languageName: node + linkType: hard + +"react-transition-group@npm:^4.4.5": + version: 4.4.5 + resolution: "react-transition-group@npm:4.4.5" + dependencies: + "@babel/runtime": "npm:^7.5.5" + dom-helpers: "npm:^5.0.1" + loose-envify: "npm:^1.4.0" + prop-types: "npm:^15.6.2" + peerDependencies: + react: ">=16.6.0" + react-dom: ">=16.6.0" + checksum: 10c0/2ba754ba748faefa15f87c96dfa700d5525054a0141de8c75763aae6734af0740e77e11261a1e8f4ffc08fd9ab78510122e05c21c2d79066c38bb6861a886c82 + languageName: node + linkType: hard + +"reflect.getprototypeof@npm:^1.0.10, reflect.getprototypeof@npm:^1.0.9": + version: 1.0.10 + resolution: "reflect.getprototypeof@npm:1.0.10" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.9" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.7" + get-proto: "npm:^1.0.1" + which-builtin-type: "npm:^1.2.1" + checksum: 10c0/7facec28c8008876f8ab98e80b7b9cb4b1e9224353fd4756dda5f2a4ab0d30fa0a5074777c6df24e1e0af463a2697513b0a11e548d99cf52f21f7bc6ba48d3ac + languageName: node + linkType: hard + +"regexp.prototype.flags@npm:^1.5.3, regexp.prototype.flags@npm:^1.5.4": + version: 1.5.4 + resolution: "regexp.prototype.flags@npm:1.5.4" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-errors: "npm:^1.3.0" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + set-function-name: "npm:^2.0.2" + checksum: 10c0/83b88e6115b4af1c537f8dabf5c3744032cb875d63bc05c288b1b8c0ef37cbe55353f95d8ca817e8843806e3e150b118bc624e4279b24b4776b4198232735a77 + languageName: node + linkType: hard + +"reselect@npm:^5.2.0": + version: 5.2.0 + resolution: "reselect@npm:5.2.0" + checksum: 10c0/d0a8497e404b25a769fe792eacae88b9b576c30870450cd243d76ec9427d91a59c4a2cc00d824d283448b444c4c698a8f961d771c8ae39c759313afb31950e2e + languageName: node + linkType: hard + +"resolve-from@npm:^4.0.0": + version: 4.0.0 + resolution: "resolve-from@npm:4.0.0" + checksum: 10c0/8408eec31a3112ef96e3746c37be7d64020cda07c03a920f5024e77290a218ea758b26ca9529fd7b1ad283947f34b2291c1c0f6aa0ed34acfdda9c6014c8d190 + languageName: node + linkType: hard + +"resolve@npm:^1.19.0": + version: 1.22.12 + resolution: "resolve@npm:1.22.12" + dependencies: + es-errors: "npm:^1.3.0" + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/b16dc9b537c02e8c3388f7d3dcff9741d3071625f9a97ac1c885f2b0ca51e78df22328fb6d6ef214dd9101fb7cfc19aa2836fe3410402a94f3f7b8639c7149bf + languageName: node + linkType: hard + +"resolve@npm:^2.0.0-next.5": + version: 2.0.0-next.7 + resolution: "resolve@npm:2.0.0-next.7" + dependencies: + es-errors: "npm:^1.3.0" + is-core-module: "npm:^2.16.2" + node-exports-info: "npm:^1.6.0" + object-keys: "npm:^1.1.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/8c6fa17ccdb826a3a52387ed8c42bf705dbc19d5494da52a86661b124b5b88fad5d8edbbb4434a1b563ecf7768368b7da9fb9489e20f36e02f7551a4ea87d707 + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^1.19.0#optional!builtin": + version: 1.22.12 + resolution: "resolve@patch:resolve@npm%3A1.22.12#optional!builtin::version=1.22.12&hash=c3c19d" + dependencies: + es-errors: "npm:^1.3.0" + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/fc6519984ae1f894d877c0060ba8b1f5ba3bc0e85a02f74e141929c118c23d74d9735619a9cc2965397387e514884245c65d72a40731dcb6cfc84c7bcdc8321e + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^2.0.0-next.5#optional!builtin": + version: 2.0.0-next.7 + resolution: "resolve@patch:resolve@npm%3A2.0.0-next.7#optional!builtin::version=2.0.0-next.7&hash=c3c19d" + dependencies: + es-errors: "npm:^1.3.0" + is-core-module: "npm:^2.16.2" + node-exports-info: "npm:^1.6.0" + object-keys: "npm:^1.1.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/6bb6f1d8a1789f7a5b4e35d950e76bc0ba632ff7d51fe86b6f34fb5f41e1ce0f7b884ec166828cfc0728ddffeaee49527711d752d12398297f90c51acd22195e + languageName: node + linkType: hard + +"rollup@npm:^4.43.0": + version: 4.62.2 + resolution: "rollup@npm:4.62.2" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.62.2" + "@rollup/rollup-android-arm64": "npm:4.62.2" + "@rollup/rollup-darwin-arm64": "npm:4.62.2" + "@rollup/rollup-darwin-x64": "npm:4.62.2" + "@rollup/rollup-freebsd-arm64": "npm:4.62.2" + "@rollup/rollup-freebsd-x64": "npm:4.62.2" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.62.2" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.62.2" + "@rollup/rollup-linux-arm64-gnu": "npm:4.62.2" + "@rollup/rollup-linux-arm64-musl": "npm:4.62.2" + "@rollup/rollup-linux-loong64-gnu": "npm:4.62.2" + "@rollup/rollup-linux-loong64-musl": "npm:4.62.2" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.62.2" + "@rollup/rollup-linux-ppc64-musl": "npm:4.62.2" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.62.2" + "@rollup/rollup-linux-riscv64-musl": "npm:4.62.2" + "@rollup/rollup-linux-s390x-gnu": "npm:4.62.2" + "@rollup/rollup-linux-x64-gnu": "npm:4.62.2" + "@rollup/rollup-linux-x64-musl": "npm:4.62.2" + "@rollup/rollup-openbsd-x64": "npm:4.62.2" + "@rollup/rollup-openharmony-arm64": "npm:4.62.2" + "@rollup/rollup-win32-arm64-msvc": "npm:4.62.2" + "@rollup/rollup-win32-ia32-msvc": "npm:4.62.2" + "@rollup/rollup-win32-x64-gnu": "npm:4.62.2" + "@rollup/rollup-win32-x64-msvc": "npm:4.62.2" + "@types/estree": "npm:1.0.9" + fsevents: "npm:~2.3.2" + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-freebsd-arm64": + optional: true + "@rollup/rollup-freebsd-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-loong64-gnu": + optional: true + "@rollup/rollup-linux-loong64-musl": + optional: true + "@rollup/rollup-linux-ppc64-gnu": + optional: true + "@rollup/rollup-linux-ppc64-musl": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-riscv64-musl": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-openbsd-x64": + optional: true + "@rollup/rollup-openharmony-arm64": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-gnu": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 10c0/83ff5f4a1fea3fa05db2ef56beceee8c33d4a72b818e19c562f1e85c41076fe5b12aadc44048bb73e60b83336df82154b017fa6bf0186f3141643e6f215fbdcb + languageName: node + linkType: hard + +"root-workspace-0b6124@workspace:.": + version: 0.0.0-use.local + resolution: "root-workspace-0b6124@workspace:." + dependencies: + "@babel/core": "npm:^8.0.1" + "@base-ui/react": "npm:^1.6.0" + "@emotion/react": "npm:^11.14.0" + "@emotion/styled": "npm:^11.14.1" + "@fontsource/roboto": "npm:^5.2.10" + "@mui/icons-material": "npm:^9.1.1" + "@mui/material": "npm:^9.1.2" + "@preact/preset-vite": "npm:^2.10.2" + axios: "npm:^1.18.1" + eslint: "npm:^10.5.0" + eslint-config-preact: "npm:^2.0.0" + preact: "npm:^10.26.9" + typescript: "npm:^6.0.3" + vite: "npm:^7.0.4" + languageName: unknown + linkType: soft + +"safe-array-concat@npm:^1.1.3": + version: 1.1.4 + resolution: "safe-array-concat@npm:1.1.4" + dependencies: + call-bind: "npm:^1.0.9" + call-bound: "npm:^1.0.4" + get-intrinsic: "npm:^1.3.0" + has-symbols: "npm:^1.1.0" + isarray: "npm:^2.0.5" + checksum: 10c0/95fb4904ab1d9360a666fe5ba6d88f1c4a3a39682739e4512cff809fc6b5722a94bd95189211015bfb45859a7ffbc3340ea303ae22721c91c59e8946d310975a + languageName: node + linkType: hard + +"safe-push-apply@npm:^1.0.0": + version: 1.0.0 + resolution: "safe-push-apply@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + isarray: "npm:^2.0.5" + checksum: 10c0/831f1c9aae7436429e7862c7e46f847dfe490afac20d0ee61bae06108dbf5c745a0de3568ada30ccdd3eeb0864ca8331b2eef703abd69bfea0745b21fd320750 + languageName: node + linkType: hard + +"safe-regex-test@npm:^1.1.0": + version: 1.1.0 + resolution: "safe-regex-test@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + is-regex: "npm:^1.2.1" + checksum: 10c0/f2c25281bbe5d39cddbbce7f86fca5ea9b3ce3354ea6cd7c81c31b006a5a9fff4286acc5450a3b9122c56c33eba69c56b9131ad751457b2b4a585825e6a10665 + languageName: node + linkType: hard + +"semver@npm:^6.3.1": + version: 6.3.1 + resolution: "semver@npm:6.3.1" + bin: + semver: bin/semver.js + checksum: 10c0/e3d79b609071caa78bcb6ce2ad81c7966a46a7431d9d58b8800cfa9cb6a63699b3899a0e4bcce36167a284578212d9ae6942b6929ba4aa5015c079a67751d42d + languageName: node + linkType: hard + +"semver@npm:^7.3.5, semver@npm:^7.7.3": + version: 7.8.5 + resolution: "semver@npm:7.8.5" + bin: + semver: bin/semver.js + checksum: 10c0/b1f3127a5be8125a94f37188b361c212466c292c6910adce3ec106cff5dc211ccaedc4739c11bb70fda59d6fc1f040a9bca289f4e093451521a2372e5231fe0c + languageName: node + linkType: hard + +"semver@npm:^7.6.2": + version: 7.8.4 + resolution: "semver@npm:7.8.4" + bin: + semver: bin/semver.js + checksum: 10c0/81b7c296fd7927b80f67fa516b75fa1017caac8167795320de28e76ccbc6f7f01763c30ecd10d6a0d8fd089708ab0548a5aebb94b0870e99c2a2b4600a46389b + languageName: node + linkType: hard + +"set-function-length@npm:^1.2.2": + version: 1.2.2 + resolution: "set-function-length@npm:1.2.2" + dependencies: + define-data-property: "npm:^1.1.4" + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.2.4" + gopd: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.2" + checksum: 10c0/82850e62f412a258b71e123d4ed3873fa9377c216809551192bb6769329340176f109c2eeae8c22a8d386c76739855f78e8716515c818bcaef384b51110f0f3c + languageName: node + linkType: hard + +"set-function-name@npm:^2.0.2": + version: 2.0.2 + resolution: "set-function-name@npm:2.0.2" + dependencies: + define-data-property: "npm:^1.1.4" + es-errors: "npm:^1.3.0" + functions-have-names: "npm:^1.2.3" + has-property-descriptors: "npm:^1.0.2" + checksum: 10c0/fce59f90696c450a8523e754abb305e2b8c73586452619c2bad5f7bf38c7b6b4651895c9db895679c5bef9554339cf3ef1c329b66ece3eda7255785fbe299316 + languageName: node + linkType: hard + +"set-proto@npm:^1.0.0": + version: 1.0.0 + resolution: "set-proto@npm:1.0.0" + dependencies: + dunder-proto: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/ca5c3ccbba479d07c30460e367e66337cec825560b11e8ba9c5ebe13a2a0d6021ae34eddf94ff3dfe17a3104dc1f191519cb6c48378b503e5c3f36393938776a + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: "npm:^3.0.0" + checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 + languageName: node + linkType: hard + +"side-channel-list@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-list@npm:1.0.1" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.4" + checksum: 10c0/d346c787fd2f9f1c2fdea14f00e8250118db0e7596d85a6cb9faa75f105d31a73a8f7a341c93d7df2a2429098c3d37a77bd3be9e88c37094b8c01807bc77c7a2 + languageName: node + linkType: hard + +"side-channel-map@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-map@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + checksum: 10c0/010584e6444dd8a20b85bc926d934424bd809e1a3af941cace229f7fdcb751aada0fb7164f60c2e22292b7fa3c0ff0bce237081fd4cdbc80de1dc68e95430672 + languageName: node + linkType: hard + +"side-channel-weakmap@npm:^1.0.2": + version: 1.0.2 + resolution: "side-channel-weakmap@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + side-channel-map: "npm:^1.0.1" + checksum: 10c0/71362709ac233e08807ccd980101c3e2d7efe849edc51455030327b059f6c4d292c237f94dc0685031dd11c07dd17a68afde235d6cf2102d949567f98ab58185 + languageName: node + linkType: hard + +"side-channel@npm:^1.1.0": + version: 1.1.1 + resolution: "side-channel@npm:1.1.1" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.4" + side-channel-list: "npm:^1.0.1" + side-channel-map: "npm:^1.0.1" + side-channel-weakmap: "npm:^1.0.2" + checksum: 10c0/dc0ab81d67f61bda9247d053ce93f41c3fd8ad2bdcb9cf9d8d2f8540d488f26d87a5e99ebfc07eea49ec025867b2452b705442d974b1478f0395e69f6bfb3270 + languageName: node + linkType: hard + +"simple-code-frame@npm:^1.3.0": + version: 1.3.0 + resolution: "simple-code-frame@npm:1.3.0" + dependencies: + kolorist: "npm:^1.6.0" + checksum: 10c0/f74332d839689b30deddc079f52845c05e455160888c2076056dd070d0088b8e84a0f36b6d273d38a8698e738dc5f34bbe6ecdcc86146b9d4250c67374f975c5 + languageName: node + linkType: hard + +"source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf + languageName: node + linkType: hard + +"source-map@npm:^0.5.7": + version: 0.5.7 + resolution: "source-map@npm:0.5.7" + checksum: 10c0/904e767bb9c494929be013017380cbba013637da1b28e5943b566031e29df04fba57edf3f093e0914be094648b577372bd8ad247fa98cfba9c600794cd16b599 + languageName: node + linkType: hard + +"source-map@npm:^0.7.4": + version: 0.7.6 + resolution: "source-map@npm:0.7.6" + checksum: 10c0/59f6f05538539b274ba771d2e9e32f6c65451982510564438e048bc1352f019c6efcdc6dd07909b1968144941c14015c2c7d4369fb7c4d7d53ae769716dcc16c + languageName: node + linkType: hard + +"stack-trace@npm:^1.0.0-pre2": + version: 1.0.0 + resolution: "stack-trace@npm:1.0.0" + checksum: 10c0/630e0a95137eeed8051b5359e0978baf86ae6b12f9ad8d4056398afb39c3953e39d94e104c415f4116b930527946c03df572414f289d435bf5f778490593a9de + languageName: node + linkType: hard + +"stop-iteration-iterator@npm:^1.1.0": + version: 1.1.0 + resolution: "stop-iteration-iterator@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + internal-slot: "npm:^1.1.0" + checksum: 10c0/de4e45706bb4c0354a4b1122a2b8cc45a639e86206807ce0baf390ee9218d3ef181923fa4d2b67443367c491aa255c5fbaa64bb74648e3c5b48299928af86c09 + languageName: node + linkType: hard + +"string.prototype.matchall@npm:^4.0.12": + version: 4.0.12 + resolution: "string.prototype.matchall@npm:4.0.12" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.6" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.6" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + internal-slot: "npm:^1.1.0" + regexp.prototype.flags: "npm:^1.5.3" + set-function-name: "npm:^2.0.2" + side-channel: "npm:^1.1.0" + checksum: 10c0/1a53328ada73f4a77f1fdf1c79414700cf718d0a8ef6672af5603e709d26a24f2181208144aed7e858b1bcc1a0d08567a570abfb45567db4ae47637ed2c2f85c + languageName: node + linkType: hard + +"string.prototype.repeat@npm:^1.0.0": + version: 1.0.0 + resolution: "string.prototype.repeat@npm:1.0.0" + dependencies: + define-properties: "npm:^1.1.3" + es-abstract: "npm:^1.17.5" + checksum: 10c0/94c7978566cffa1327d470fd924366438af9b04b497c43a9805e476e2e908aa37a1fd34cc0911156c17556dab62159d12c7b92b3cc304c3e1281fe4c8e668f40 + languageName: node + linkType: hard + +"string.prototype.trim@npm:^1.2.10": + version: 1.2.11 + resolution: "string.prototype.trim@npm:1.2.11" + dependencies: + call-bind: "npm:^1.0.9" + call-bound: "npm:^1.0.4" + define-data-property: "npm:^1.1.4" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.24.2" + es-object-atoms: "npm:^1.1.2" + has-property-descriptors: "npm:^1.0.2" + safe-regex-test: "npm:^1.1.0" + checksum: 10c0/b153cf8ed06db82ff40e27829e88e5c13f45eff9799f1d5707626e25989b488b059d6f5d57011e07f77745e28451e16735f295bc59c8ae146a4fd73a442366b0 + languageName: node + linkType: hard + +"string.prototype.trimend@npm:^1.0.9": + version: 1.0.10 + resolution: "string.prototype.trimend@npm:1.0.10" + dependencies: + call-bind: "npm:^1.0.9" + call-bound: "npm:^1.0.4" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.1.2" + checksum: 10c0/cc09233181769047a5330becfd5740fec5f0c8137886e7b553626788b00f75df9f34db1159bc52dbb7fc389b8ebb6e1dab44c8c9e31eb600039729a542013286 + languageName: node + linkType: hard + +"string.prototype.trimstart@npm:^1.0.8": + version: 1.0.8 + resolution: "string.prototype.trimstart@npm:1.0.8" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/d53af1899959e53c83b64a5fd120be93e067da740e7e75acb433849aa640782fb6c7d4cd5b84c954c84413745a3764df135a8afeb22908b86a835290788d8366 + languageName: node + linkType: hard + +"stylis@npm:4.2.0": + version: 4.2.0 + resolution: "stylis@npm:4.2.0" + checksum: 10c0/a7128ad5a8ed72652c6eba46bed4f416521bc9745a460ef5741edc725252cebf36ee45e33a8615a7057403c93df0866ab9ee955960792db210bb80abd5ac6543 + languageName: node + linkType: hard + +"supports-preserve-symlinks-flag@npm:^1.0.0": + version: 1.0.0 + resolution: "supports-preserve-symlinks-flag@npm:1.0.0" + checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 + languageName: node + linkType: hard + +"tar@npm:^7.5.4": + version: 7.5.16 + resolution: "tar@npm:7.5.16" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.1.0" + yallist: "npm:^5.0.0" + checksum: 10c0/4f37f3c4bd2ca2755fd736a5df1d573c1a868ec1b1e893346aeafa95ac510f9e2fd1469420bd866cc7904799e5bd4ac62b5d4f03fe27747d6e1e373b44505c5c + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.15": + version: 0.2.17 + resolution: "tinyglobby@npm:0.2.17" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.4" + checksum: 10c0/7f7bb0f197c88bc4b20c231e0deca4240ca3bf313a88f5a7fee93a872b84966a4d50220947c0455ad07a60b3b360961c5b7fd979222aeb716a9f99b412002e4c + languageName: node + linkType: hard + +"type-check@npm:^0.4.0, type-check@npm:~0.4.0": + version: 0.4.0 + resolution: "type-check@npm:0.4.0" + dependencies: + prelude-ls: "npm:^1.2.1" + checksum: 10c0/7b3fd0ed43891e2080bf0c5c504b418fbb3e5c7b9708d3d015037ba2e6323a28152ec163bcb65212741fa5d2022e3075ac3c76440dbd344c9035f818e8ecee58 + languageName: node + linkType: hard + +"typed-array-buffer@npm:^1.0.3": + version: 1.0.3 + resolution: "typed-array-buffer@npm:1.0.3" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-typed-array: "npm:^1.1.14" + checksum: 10c0/1105071756eb248774bc71646bfe45b682efcad93b55532c6ffa4518969fb6241354e4aa62af679ae83899ec296d69ef88f1f3763657cdb3a4d29321f7b83079 + languageName: node + linkType: hard + +"typed-array-byte-length@npm:^1.0.3": + version: 1.0.3 + resolution: "typed-array-byte-length@npm:1.0.3" + dependencies: + call-bind: "npm:^1.0.8" + for-each: "npm:^0.3.3" + gopd: "npm:^1.2.0" + has-proto: "npm:^1.2.0" + is-typed-array: "npm:^1.1.14" + checksum: 10c0/6ae083c6f0354f1fce18b90b243343b9982affd8d839c57bbd2c174a5d5dc71be9eb7019ffd12628a96a4815e7afa85d718d6f1e758615151d5f35df841ffb3e + languageName: node + linkType: hard + +"typed-array-byte-offset@npm:^1.0.4": + version: 1.0.4 + resolution: "typed-array-byte-offset@npm:1.0.4" + dependencies: + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + for-each: "npm:^0.3.3" + gopd: "npm:^1.2.0" + has-proto: "npm:^1.2.0" + is-typed-array: "npm:^1.1.15" + reflect.getprototypeof: "npm:^1.0.9" + checksum: 10c0/3d805b050c0c33b51719ee52de17c1cd8e6a571abdf0fffb110e45e8dd87a657e8b56eee94b776b13006d3d347a0c18a730b903cf05293ab6d92e99ff8f77e53 + languageName: node + linkType: hard + +"typed-array-length@npm:^1.0.7": + version: 1.0.8 + resolution: "typed-array-length@npm:1.0.8" + dependencies: + call-bind: "npm:^1.0.9" + for-each: "npm:^0.3.5" + gopd: "npm:^1.2.0" + is-typed-array: "npm:^1.1.15" + possible-typed-array-names: "npm:^1.1.0" + reflect.getprototypeof: "npm:^1.0.10" + checksum: 10c0/5319f740fc426a3217182c2f7c87656acb0903e046de5a938e30167337d26abf1bb3ad4b32833a72521a4cc58223aec80627b38b357d0a3d5fd64881427e77ab + languageName: node + linkType: hard + +"typescript@npm:^6.0.3": + version: 6.0.3 + resolution: "typescript@npm:6.0.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/4a25ff5045b984370f48f196b3a0120779b1b343d40b9a68d114ea5e5fff099809b2bb777576991a63a5cd59cf7bffd96ff6fe10afcefbcb8bd6fb96ad4b6606 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^6.0.3#optional!builtin": + version: 6.0.3 + resolution: "typescript@patch:typescript@npm%3A6.0.3#optional!builtin::version=6.0.3&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/2f25c74e65663c248fa1ade2b8459d9ce5372ff9dad07067310f132966ebec1d93f6c42f0baf77a6b6a7a91460463f708e6887013aaade22111037457c6b25df + languageName: node + linkType: hard + +"unbox-primitive@npm:^1.1.0": + version: 1.1.0 + resolution: "unbox-primitive@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.3" + has-bigints: "npm:^1.0.2" + has-symbols: "npm:^1.1.0" + which-boxed-primitive: "npm:^1.1.1" + checksum: 10c0/7dbd35ab02b0e05fe07136c72cb9355091242455473ec15057c11430129bab38b7b3624019b8778d02a881c13de44d63cd02d122ee782fb519e1de7775b5b982 + languageName: node + linkType: hard + +"undici@npm:^6.25.0": + version: 6.27.0 + resolution: "undici@npm:6.27.0" + checksum: 10c0/f88c3dae3957dbf9d93cb481440aced317bd3c4941b5914fea5efba516d51138988cdb5c76006f0bb1337e41d56c3443351055d492e73af2428521c37ba2a76f + languageName: node + linkType: hard + +"update-browserslist-db@npm:^1.2.3": + version: 1.2.3 + resolution: "update-browserslist-db@npm:1.2.3" + dependencies: + escalade: "npm:^3.2.0" + picocolors: "npm:^1.1.1" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 10c0/13a00355ea822388f68af57410ce3255941d5fb9b7c49342c4709a07c9f230bbef7f7499ae0ca7e0de532e79a82cc0c4edbd125f1a323a1845bf914efddf8bec + languageName: node + linkType: hard + +"uri-js@npm:^4.2.2": + version: 4.4.1 + resolution: "uri-js@npm:4.4.1" + dependencies: + punycode: "npm:^2.1.0" + checksum: 10c0/4ef57b45aa820d7ac6496e9208559986c665e49447cb072744c13b66925a362d96dd5a46c4530a6b8e203e5db5fe849369444440cb22ecfc26c679359e5dfa3c + languageName: node + linkType: hard + +"use-sync-external-store@npm:^1.6.0": + version: 1.6.0 + resolution: "use-sync-external-store@npm:1.6.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/35e1179f872a53227bdf8a827f7911da4c37c0f4091c29b76b1e32473d1670ebe7bcd880b808b7549ba9a5605c233350f800ffab963ee4a4ee346ee983b6019b + languageName: node + linkType: hard + +"vite-prerender-plugin@npm:^0.5.8": + version: 0.5.13 + resolution: "vite-prerender-plugin@npm:0.5.13" + dependencies: + kolorist: "npm:^1.8.0" + magic-string: "npm:0.x >= 0.26.0" + node-html-parser: "npm:^6.1.12" + simple-code-frame: "npm:^1.3.0" + source-map: "npm:^0.7.4" + stack-trace: "npm:^1.0.0-pre2" + peerDependencies: + vite: 5.x || 6.x || 7.x || 8.x + checksum: 10c0/474feed1faac452f62c501330f0109e9c2a9ddac84bd27acc4ecb8ff92f1f0670abc1949ed8eff45957108ce65b94cd24f3b1ceae45cf3ffa75e11e0591f4bc4 + languageName: node + linkType: hard + +"vite@npm:^7.0.4": + version: 7.3.5 + resolution: "vite@npm:7.3.5" + dependencies: + esbuild: "npm:^0.27.0" + fdir: "npm:^6.5.0" + fsevents: "npm:~2.3.3" + picomatch: "npm:^4.0.3" + postcss: "npm:^8.5.6" + rollup: "npm:^4.43.0" + tinyglobby: "npm:^0.2.15" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + jiti: ">=1.21.0" + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + 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 + bin: + vite: bin/vite.js + checksum: 10c0/4ad700649ed2ebd1e726d32f3f6e41eecbec4e29bcb5977805f3d43a5659280518aa431b0fc61adc1879df4fe1978d7cfc7dbbe54cdf014022385052967721e8 + languageName: node + linkType: hard + +"which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1": + version: 1.1.1 + resolution: "which-boxed-primitive@npm:1.1.1" + dependencies: + is-bigint: "npm:^1.1.0" + is-boolean-object: "npm:^1.2.1" + is-number-object: "npm:^1.1.1" + is-string: "npm:^1.1.1" + is-symbol: "npm:^1.1.1" + checksum: 10c0/aceea8ede3b08dede7dce168f3883323f7c62272b49801716e8332ff750e7ae59a511ae088840bc6874f16c1b7fd296c05c949b0e5b357bfe3c431b98c417abe + languageName: node + linkType: hard + +"which-builtin-type@npm:^1.2.1": + version: 1.2.1 + resolution: "which-builtin-type@npm:1.2.1" + dependencies: + call-bound: "npm:^1.0.2" + function.prototype.name: "npm:^1.1.6" + has-tostringtag: "npm:^1.0.2" + is-async-function: "npm:^2.0.0" + is-date-object: "npm:^1.1.0" + is-finalizationregistry: "npm:^1.1.0" + is-generator-function: "npm:^1.0.10" + is-regex: "npm:^1.2.1" + is-weakref: "npm:^1.0.2" + isarray: "npm:^2.0.5" + which-boxed-primitive: "npm:^1.1.0" + which-collection: "npm:^1.0.2" + which-typed-array: "npm:^1.1.16" + checksum: 10c0/8dcf323c45e5c27887800df42fbe0431d0b66b1163849bb7d46b5a730ad6a96ee8bfe827d078303f825537844ebf20c02459de41239a0a9805e2fcb3cae0d471 + languageName: node + linkType: hard + +"which-collection@npm:^1.0.2": + version: 1.0.2 + resolution: "which-collection@npm:1.0.2" + dependencies: + is-map: "npm:^2.0.3" + is-set: "npm:^2.0.3" + is-weakmap: "npm:^2.0.2" + is-weakset: "npm:^2.0.3" + checksum: 10c0/3345fde20964525a04cdf7c4a96821f85f0cc198f1b2ecb4576e08096746d129eb133571998fe121c77782ac8f21cbd67745a3d35ce100d26d4e684c142ea1f2 + languageName: node + linkType: hard + +"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.19": + version: 1.1.22 + resolution: "which-typed-array@npm:1.1.22" + dependencies: + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.9" + call-bound: "npm:^1.0.4" + for-each: "npm:^0.3.5" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/e59db184a4e78b461fac3b05fafc1e7badbbedafbf04a967ee1de73717f1f9723a79699e7b5de71d449541cb5da8353efc01a4f8a72a152479850e54fa196c40 + languageName: node + linkType: hard + +"which@npm:^2.0.1": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: "npm:^2.0.0" + bin: + node-which: ./bin/node-which + checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f + languageName: node + linkType: hard + +"which@npm:^7.0.0": + version: 7.0.0 + resolution: "which@npm:7.0.0" + dependencies: + isexe: "npm:^4.0.0" + bin: + node-which: bin/which.js + checksum: 10c0/ca0b54f198f78bbc4b7c02e34bda8d335cb352e0adb4cbca1c37b1a957af3a879a82c4c27ca6525bc942f548d8b64f816ef6528360af9f3de55ffb9b979b620d + languageName: node + linkType: hard + +"word-wrap@npm:^1.2.5": + version: 1.2.5 + resolution: "word-wrap@npm:1.2.5" + checksum: 10c0/e0e4a1ca27599c92a6ca4c32260e8a92e8a44f4ef6ef93f803f8ed823f486e0889fc0b93be4db59c8d51b3064951d25e43d434e95dc8c960cc3a63d65d00ba20 + languageName: node + linkType: hard + +"yallist@npm:^3.0.2": + version: 3.1.1 + resolution: "yallist@npm:3.1.1" + checksum: 10c0/c66a5c46bc89af1625476f7f0f2ec3653c1a1791d2f9407cfb4c2ba812a1e1c9941416d71ba9719876530e3340a99925f697142989371b72d93b9ee628afd8c1 + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 + languageName: node + linkType: hard + +"yaml@npm:^1.10.0": + version: 1.10.3 + resolution: "yaml@npm:1.10.3" + checksum: 10c0/c309ff85a0a569a981d71ab9cf0fef68672a16b9cdf40639d1c3b30034f6cd16ee428602bd6d64ecf006f8c8bee499023cac236538f79898aa99fb5db529a2ed + languageName: node + linkType: hard + +"yocto-queue@npm:^0.1.0": + version: 0.1.0 + resolution: "yocto-queue@npm:0.1.0" + checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f + languageName: node + linkType: hard + +"zimmerframe@npm:^1.1.4": + version: 1.1.4 + resolution: "zimmerframe@npm:1.1.4" + checksum: 10c0/9470cbf22cefae975ab413c7158a119d082b354ddcf0da48a842f2f42246fa15943cd9b92c047de39db38015e3b866e32f383bc217e8e4f4192945c7d425536b + languageName: node + linkType: hard