From 549641c26a1ec0b6e240deb5772c7268d66728ca Mon Sep 17 00:00:00 2001 From: J Boddey Date: Tue, 20 Aug 2024 16:56:06 +0100 Subject: [PATCH 01/12] Release v1.4 into main (#687) * Update requirements.txt (#429) Signed-off-by: J Boddey * Remove in progress status after discovery (#422) * Add steps to resolve to PDF report (#411) * Add progress for steps to resolve * Formatting * Remove print statements * Allow for no steps to resolve * Add multipage * Fix multipage * Fix config * update report unit tests --------- Signed-off-by: J Boddey Co-authored-by: jhughesbiot * Bump ejs in /modules/ui in the npm_and_yarn group across 1 directory (#428) Bumps the npm_and_yarn group with 1 update in the /modules/ui directory: [ejs](https://github.com/mde/ejs). Updates `ejs` from 3.1.9 to 3.1.10 - [Release notes](https://github.com/mde/ejs/releases) - [Commits](https://github.com/mde/ejs/compare/v3.1.9...v3.1.10) --- updated-dependencies: - dependency-name: ejs dependency-type: indirect dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * For delete report take mac_addr from upper level (#436) * Update dependencies (#435) * Add new mac addr field for report deleting (#432) * Bump version to v1.2.2 (#433) * fix: replace Feature Not Present to Feature Not Detected (#445) Co-authored-by: Volha Mardvilka * Fix PDF alignment (#441) * Do not remove form control on destroy as it causes error; call system/config on settings open (#442) * Fix some pylint issues (#437) * Update documentation (#448) * Update documentation * Update docs * V1.3 (#393) * Adds version analytics event (#306) * Techdebt: adds state for testrun page (#392) * Fix tests (#397) * Fix tests * Update node version * 331379891: (feat) disable connection settings when testrun is in progress (#371) * 331379891: (feat) disable connection settings when testrun is in progress * 331379891: (fix) include more testrun results as progress * 331379891: (fix) fix spelling * 333349715: (fix) GAR 1.3 The disabled system settings panel contains a focusable element (#388) Co-authored-by: Volha Mardvilka --------- Co-authored-by: Volha Mardvilka * Disable device item if device in progress (#370) * Adds ga to track testrun initiation (#415) Adds ga ti track testrun initiation * Fix function return value (#421) * Fix redirect + adds google analytics (#420) * Adds certificates drawer component with list of certificates (#414) Adds certificates drawer component with list of certificates. Upload and delete is out of scope * List modules (#425) * Adds delete certificate (#424) Adds delete certificate * Adds class for links to track report type on download; adds event when download report is clicked on progress page (#434) * Adds upload certificate (#431) * Adds upload certificate * move delete functionality to certificates store * Catch error on report/certificate deletion (#438) * Fix button size; fix text-overflow (#440) * Adds focus on next or close button when certificate is deleted (#439) * Adds focus on next or close button when certificate is deleted * Enable test modules for initiate test run dialog (#400) Enable test modules for initiate test run dialog * Add steps to resolve to PDF report (#411) * Add progress for steps to resolve * Formatting * Remove print statements * Allow for no steps to resolve * Add multipage * Fix multipage * Fix config * update report unit tests --------- Signed-off-by: J Boddey Co-authored-by: jhughesbiot * Add new mac addr field for report deleting (#432) * Bump version to v1.2.2 (#433) * 337012359: (feat) add snackBar with wait or stop testrun (#444) * 337012359: (feat) add snackBar with wait or stop testrun * 337012359: (fix) update to fix failed tests * 337012359: (fix) add fix for deletCertificate test --------- Co-authored-by: Volha Mardvilka * Feature/port stats (#430) * Add port speed and duplex tests Add unit tests for port stats testing Add place holder for ip port link test * Add ethtool to system dependencies Restructure conn stats methods and tests Resolve port stat information for link test * Fix runtime issue * Implement link test Fix unit tests Misc port stats updates * fix pylint issues * update readme * pylint fixes --------- Signed-off-by: J Boddey Co-authored-by: J Boddey * 339315842: (feat) add risk assessment tab (#450) * 339315842: (feat) add risk assessment tab * 339315842: (feat) add risk assessment tab --------- Co-authored-by: Volha Mardvilka * Add steps to resolve to PDF report (#411) * Add progress for steps to resolve * Formatting * Remove print statements * Allow for no steps to resolve * Add multipage * Fix multipage * Fix config * update report unit tests --------- Signed-off-by: J Boddey Co-authored-by: jhughesbiot * Add new mac addr field for report deleting (#432) * Remove rebase error --------- Signed-off-by: J Boddey Co-authored-by: Olga Mardvilko Co-authored-by: Volha Mardvilka Co-authored-by: J Boddey Co-authored-by: jhughesbiot Co-authored-by: jhughesbiot <50999916+jhughesbiot@users.noreply.github.com> * Rename "history" to "reports" (#456) * Rename "device-repository" to "devices" (#455) * Change certificates endpoints (#458) * 339311887: (feat) display saved risk profile (#460) Co-authored-by: Volha Mardvilka * Fix ui defects (#459) * 340835710: (fix) [Risk assessment] change page view when callout is visible (#461) Co-authored-by: Volha Mardvilka * Fix style to allow screen reader to read label (#462) * Updaate device test module configuration from api start endpoint (#463) * Add get profiles format endpoint (#465) * 339311250: (feat) delete risk profile (#466) Co-authored-by: Volha Mardvilka * 341254121: (fix) [a11y] add item name for aria-labels delete and copy buttons (#467) Co-authored-by: Volha Mardvilka * Make testing statuses available outside of Testing Tab (#468) * Adds timeout token * Status is available on all pages; Removed unused field isTestrunStarted - actual status is available * Validate CA certificate on FE (#464) * Adds validation on certificate upload * Show notification when error happens * 341901606: (fix) [GAR 1.6]: update for arrows usage on risk profile (#471) Co-authored-by: Volha Mardvilka * Adds space in certificate name regexp (#469) * 342096458: (fix) [GAR 1.3] add focus trap to certificates panel to prevent move focus to risk profile (#472) Co-authored-by: Volha Mardvilka * The focus is not moved to the snackbar with certificate validation rule (#470) * Adds class to fix focus flow * Changes the delay according to string length * Adds file name in error message (#473) * Add certificate endpoints (#451) * Adds version analytics event (#306) * Techdebt: adds state for testrun page (#392) * Fix tests (#397) * Fix tests * Update node version * 331379891: (feat) disable connection settings when testrun is in progress (#371) * 331379891: (feat) disable connection settings when testrun is in progress * 331379891: (fix) include more testrun results as progress * 331379891: (fix) fix spelling * 333349715: (fix) GAR 1.3 The disabled system settings panel contains a focusable element (#388) Co-authored-by: Volha Mardvilka --------- Co-authored-by: Volha Mardvilka * Disable device item if device in progress (#370) * Adds ga to track testrun initiation (#415) Adds ga ti track testrun initiation * Certs progress * Add list and upload cert endpoint * Add delete certificate endpoint * Update cert response codes * Upgrade dependency --------- Co-authored-by: Sofia Kurilova Co-authored-by: Olga Mardvilko Co-authored-by: Volha Mardvilka * Fix modify devices test (#449) * Fix modify devices * Remove excess logging * "Waiting for Device" :the snack bar appears on all pages (#474) * Make waiting for device snackbar global * Do not dismiss snack bar onDestroy * Rename testrun component (#480) * Rename settings component (#476) * Rename testrun component (#477) * Rename testrun component * Improve API test coverage (#291) * Improve API test coverage * Add timeouts * Improve coverage * Increase API timeout Signed-off-by: J Boddey * Pylint fixes * Disable broken tests * Fix skip * Disable broken tests * Fix test --------- Signed-off-by: J Boddey * Remove certificate if BE error happens (#484) * Add cert status (#478) * Add ethtool to make and docs (#483) * Add exception handling to certificate upload (#479) * Add cert status * Add exception handling to cert upload * Add get profiles format endpoint (#475) * Add get profiles format endpoint * Update risk assessment format * Re-add modules endpoint * Update session.py Signed-off-by: J Boddey --------- Signed-off-by: J Boddey * Reduce locations of Testrun version (#453) * Reduce locations of Testrun version * Update from comments * Resolve make control file --------- Signed-off-by: J Boddey * Allow stop testrun from any other page (#487) * Feature/tls client protocols (#485) * Add methods to allow known protocols that dont fit into the strict tls client detection methods Add QUIC protocol to approved protocols Start moving unit tests to a docker container for consistency * Fix client connections protocol method script Downgrade cryptography and pyOpenSSL libraries Move unit testing into docker for TLS module * Misc cleanup and pylint issues --------- Co-authored-by: J Boddey * The focus goes to the main page element after adding the ceriticate (#489) * Focus first interactive element in cert container when cert snack bar closed * The snack bar with test attempt status appears if the user is not on Testing page (#488) * Remove testrun status snack bar * Update consent form (#490) * Adds opt out checkbox * Adds announce when settings or certificate panel is opened (#493) * Set status code on failed cert upload (#491) * Add risk profiles (#486) * Add get profiles format endpoint * Update risk assessment format * Re-add modules endpoint * Work on profiles * Add load profiles format * Pylint * Add check status --------- Signed-off-by: J Boddey * Add test count to PDF report (#482) * Add test count to PDF report * Fix pylint issue * Exclude error --------- Signed-off-by: J Boddey * Change network mode on network modules to fix gateway routing (#495) * 342365574: (feat) display info about Risk Assessment during testing (#492) * 342365574: (feat) display info about Risk Assessment during testing * 342365574: (fix) change to show Risk Assessment callout only on InProgress --------- Co-authored-by: Volha Mardvilka * Updates status with data from start response (#494) * 341966862: (feat) display info about risk assessment in welcome modal (#496) Co-authored-by: Volha Mardvilka * Adds download zip window (#497) * 344874424: (fix) disable device tile in Canceling status (#500) Co-authored-by: Volha Mardvilka * Fix/UI/345164706 (#501) * 345164706: (fix) disable start testrun btn in canceling state * 345164706: (fix) remove commented code --------- Co-authored-by: Volha Mardvilka * GA option issues on the Welcome modal (#502) * Fix ga initial value * Fix label * 345202815: (fix) callout with the RA message is shown on the RA page (#503) Co-authored-by: Volha Mardvilka * 345203686: (fix) update callout block view on the welcome modal (#504) Co-authored-by: Volha Mardvilka * Allow UI to specify modules (#505) * Update risk assessment format (#499) * Fix issue with checking for error result (#498) * Fix issue with checking for error result * Resolve issue * Update html in test report Signed-off-by: J Boddey --------- Signed-off-by: J Boddey * Announce disabled state of settings panel (#506) * The Downlaod ZIP action can not be performed using the keaboard (#508) * Changes aria-label for Download Anyway button * Fix keyboard navigation for Download zip component * Add create and delete profile endpoints (#507) * Work towards creating profiles * Add delete profile endpoint * Exclude link local for arp (#418) * Add feature not detected test result (#396) * Add feature not present test result * Remove changes to tls module * Further pylint fixes * Add Feature Not Present to relevant tests * Reduce pylint limit * Change to feature not detected * Modify bacnet result * Update DNS test --------- Signed-off-by: J Boddey * Add extension for cert upload (#510) * 340859666: (feat): display risk profile form with name field (#514) Co-authored-by: Volha Mardvilka * Fixes some pylint issues (#511) * Fix some pylint issues * Fix more pylint issues * bug/test_baseline (#513) * Update mac address for baseline config dynamically * fix line endings * Update device config via Testrun api * Add API to no ui mode * Fix API endpoint call * Remove mac address update Change mac address in container * Add container start command back * Skip DNS tests * Cleanup --------- Co-authored-by: J Boddey * Attach profile to ZIP report (#518) * Work towards creating profiles * Add delete profile endpoint * Work on attaching profile to zip * Pylint * Fix zipping * Pylint fixes --------- Signed-off-by: J Boddey * Generate Risk profile from json (#515) Generate form from json * The Downlaod ZIP action can not be performed using the keyboard (#517) * Stop propagating event to eliminate the error * Fix tests * Adds tooltip * Fix export endpoint to use POST request * Fix zip download --------- Co-authored-by: Jacob Boddey * Add required if applicable (#519) * WIP: Add required if applicable * Fix bug with TLS client test * Update TLS results * Fix styles for helperbird (#524) * Form from json validation (#523) * Adds field validation * Fix vulnerabilities in dependencies (#526) * Enable draft button when profile name is present; enable save button when form is valid; remove discard button (#530) * Correct result on tls client test (#528) * Add informational and fnd to report (#527) * Mark fields required when trimmed value is empty (#529) * Update requests dependency (#525) * Remove debug artifact (#531) * Fix scroll area on reports page (#532) * Announce risk form open; focus first element in container (#536) * Fix validation; change element for text-long (#538) * 345258435: (feat) display expired certificate (#539) Co-authored-by: Volha Mardvilka * 348187954: (fix) update callouts position on the small window size (#540) Co-authored-by: Volha Mardvilka * 348356236: (fix) add tooltips for icons without any accompanying text (#541) Co-authored-by: Volha Mardvilka * 348353479: (fix) add tooltip for the download zip button using the keyboard (#542) Co-authored-by: Volha Mardvilka * 348361925: (fix) add helper text for mandatory profile name field (#543) Co-authored-by: Volha Mardvilka * Save new risk profile (#533) * Save new risk profile * Clear and close form after profile saved * Adds status valid for save profile * Fix DNS report when DNS packet is missing the qname property (#546) * Adds edit risk profile (#544) * Refactor delete form: rename it to simple dialog as it is not used for delete anymore * Adds edit risk profile * Fix autozise (#547) * Feature/risk profile (#522) * Update risk profile * Add unit tests for risk profile Fix service nmap unit test * Add unit tests for risk profile Fix service nmap unit test * Update api and session for new risk profile methods * pylint * pylint * Fix some pylint * Fix more pylint * Fix some bugs * Update risk profile logic (#535) * Update risk profile logic * Update test profiles Remove duplicate test file Cleanup temp test files * Remove categories from profile * Update expiration check to account for leap years * Update risk assessment questions * Update dependency * Add missing question * Update format and add error handling --------- Co-authored-by: jhughesbiot --------- Signed-off-by: J Boddey Co-authored-by: Jacob Boddey * Adds save draft (#549) Co-authored-by: Sofia Kurilova * 346351108: (feat) display risk assessment result (#553) * Close form after selected risk profile was deleted (#554) Co-authored-by: Sofia Kurilova * Fix error when updating profile (#555) * Render informational result correctly (#551) * Re-add method for exporting profle (#550) * Fix bug when failed to fetch latest version (#548) * 349769454: (fix) update size on the empty reports page to prevent overlapping (#557) * Fix bad multiple ip report when no ip requested (#556) * Fix Duplicate Certificate Names (#545) * Check for existing common name before uploading new cert Misc pylint updates * Re-add missing session content --------- Signed-off-by: J Boddey Co-authored-by: Jacob Boddey * Changes the icon; adds create date (#552) * 349783464: (fix) add styles for selected elements and fix form size on RA (#558) * 349793005: (fix) change focus to profile form to scroll up on opening profile (#559) * Return focus on create button when new profile created; return focus on profile is profile was edited (#560) * Small refactoring; update validators on selected profile update and on profile list update (#564) * Enables save and draft button for edit mode (#565) * Validate multi select form group on last checkbox tab press (#566) * Remove copy button (#567) * Bug/bacnet device (#568) * Change BACnet device detection validation Add unit tests * Fix runtime * cleanup * Update unit tests to match runtime types * Show only valid profile in modal; fix condition to not download profile if redirect button clicked (#569) * Fix bug when saving draft profile (#563) * Fix bug when saving draft * Remove risk when status changes to draft * Re-add exception handling * Fix formatting : * Update questions before calculating risk * Update unit testing * Update unit testing * Invoke risk profile update method in favor of manually updating properties in session * Update risk after update and fix type error * Update risk correctly --------- Signed-off-by: J Boddey Co-authored-by: jhughesbiot * Focus title or first element in container when navigation is triggered by enter (#570) * Adds aria label and tooltip to risk profile icons (#572) * Add profile PDF (#562) * Work on pdf * Work on profile PDF * Fix risk answer formatting * Downgrade weasyprint * Remove duplicate line * Update risk assessment after review * Fix profile format undefined * Update zip file to use /tmp directory (#571) Update device report folder Update file paths to use old and new patterns Co-authored-by: J Boddey * Remove cert from session after delete request (#575) * Fix error when only draft profiles are exist (#576) * Focus fix; fix profile status icon (#579) * Fix focus after page is opened * Fix profile status * Support longer string answers in profile PDF (#578) * Add extra spacing for long string answers * Format JSON * Cleanup old test devices from runtime (#583) * 351758698: (fix) update app version styles to meet design on expanded nav (#586) * Update risk profile description (#582) * Bump version for release (#584) * Remove skipped result (#580) * Prevent creating device with duplicate manufacturer and model (#581) * Prevent duplicate mf and model * Change error message to be more precise * Fix logic * Remove profile from runtime once included in ZIP (#577) * Remove profile after ZIP creation * Create /tmp dir for results Copy test results and profile to results dir Zip results dir and cleanup --------- Co-authored-by: jhughesbiot * Do not show settings callout if there are no interfaces and saved settings (#587) * Fix modbus results (#588) * Don't show error if config is empty; don't show settings callout when settings are not empty after saving (#589) * Catch error to proceed with device creation/editing (#592) * Remove output logging from OS level commands (#594) * Fix cancel after monitor bug and add testrun.log (#595) * Fix step 1 callout error (#593) * Fix GAR bug with cert upload (#590) * Change exception logic on cert upload * Fix error logic * Update documentation (#591) * Work on docs changes * Update docs * Update roadmap * Update docs * Update docs * Remove dev README.md * Remove skipped from docs * Add exception handling to timestamp parsing (#598) * Change exception logic on cert upload * Fix error logic * Add exception handling to timestamp parse * Stick button to the bottom of risk page (#574) * Adds copy of risk profile (#573) * Lint fix * Set testrun IDLE status if report of finished test run is removed (#585) * Adds Discard risk profile (#596) * Load test modules dynamically (#597) * Update download zip modal: add link to Risk Profiles, remove redirect button, rename download button, add No Profile option (#599) * 351338001: (feat) update rule for the third step message (#600) * 347009372: (feat) add selector for profile options for GA4 (#604) * Adds tooltip for copy and delete; show "same name" error when more than one copy is created (#606) * 346999760: (feat) [GA4] Track CA Certificates (#607) * 353476778: (fix) GAR 2.11 change for prevent risk profile tiles overlap (#609) * If profile is editing, return focus on profile (#612) * Change title for save profile dialog according to type of profile (#610) * Get reports when app is opened and when report page is opened; change status if testrun does not exist in reports; do not change status if testrun is just finished but not in reports yet (#613) * Show tooltip only on keyboard or hover (#614) * Update condition as report field is unique (#615) * Expired profile profile (#619) * Use device mac_addr if report mac_addr is missing (#622) * get system network interfaces util func * test get_sys_interfaces * pylint * get all network interfaces on session start * dicts diff * detect network adapter change * pylint * logging * Add ws server * Add MQTT protocol * Upgrade ws server * paho-mqtt dependency * getting docker container IP by container name * MQTT client class * Fix pylint * rename mqtt client logger * APScheduler * initializing the client inside the testrun object * pylint * check networks adapters in background * remove extra lines at the end of a file * rename mgtt logger * move network_adapters_checker to network_orchestrator * Adds mqtt client, adds pop up when new adapter is available (#603) * Fix some pylint issues (#620) * Disabled test results (#212) * Better handling of disbled tests Add dns and tls tests back in disabled state * pyling fixes * Fix subscriptable error * Re-add old tests as informational --------- Co-authored-by: J Boddey * remove get ip of docker container because it is not necessary * Fix individual test disabling when run from the UI (#629) * Fix individual test disabling when run from the UI * Remove disabled result for now --------- Co-authored-by: Jacob Boddey * Update package.yml (#624) Signed-off-by: J Boddey * Added risk profile api testing (#628) * added tests for profile endpoints * Modified test_start_testrun_started_successfully payload to match the expected json format, updated the profile endpoints tests * fixed the pylint errors from test_api.py * fixed few more pylint errors * Expired profile profile (#619) * Use device mac_addr if report mac_addr is missing (#622) * Fix some pylint issues (#620) * Disabled test results (#212) * Better handling of disbled tests Add dns and tls tests back in disabled state * pyling fixes * Fix subscriptable error * Re-add old tests as informational --------- Co-authored-by: J Boddey * added try-except block to delete_all_profiles() * Revert session file back * Add new line * Update api testing * updated the tests for profile endpoint, added a new fixture (add_profile) to create a profile * Updated profile endpoints: created a new fixture add_profile for creating profiles * Fix pylint * Fix pylint * Fix pylint issues --------- Co-authored-by: Sofia Kurilova Co-authored-by: J Boddey Co-authored-by: jhughesbiot * Adds close button for expired profile (#635) * Copy changes from hotfix 1.3.1 to dev (#631) * Copy changes from hotfix 1.3.1 to dev * Add pydyf version Signed-off-by: J Boddey --------- Signed-off-by: J Boddey * Inform FE about a new network adapter discovered( rename mqtt topic according to naming convention) (#634) * rename mqtt topic * Rename topic in ui --------- Co-authored-by: kurilova Co-authored-by: J Boddey * Fix network only mode issues (#617) Co-authored-by: J Boddey * Update all unit tests to work within the runtime environment (#611) * Update all unit tests to work within the runtime environment * Fix some formatting * fix line endings * update gitignore * Fix binary files in dockerfile * Change unit tests to run from testrun root directory * Run unit tests in actions * Fix pylint issues * Change command in actions * Update testing.yml Signed-off-by: J Boddey --------- Signed-off-by: J Boddey Co-authored-by: Jacob Boddey * Feature/dns report update (#637) * Update DNS module report Downgrade python packages for tls module * Fix header * pylint fixes * refactor func to handle case when network interface not exists * set test result "Error" * check device connected * thread for monitoring device connection * Minor changes * check the device connection only before each test * Adds tooltip (#638) Adds tooltip * send testrun status using mqtt * remove duplicatied line * refactor setting remaining tests to error * pylint * Fix focus after profile delete - track by name (#640) Fix focus after profile delete - track by name * Update the requests dependency (#643) * Update requests dependency * Update requests dependency * Update dependency in TLS test * Update docker dependency --------- Signed-off-by: J Boddey * Revert "Expired profile (#619)" (#645) Prevent opening of Expired risk profile * Improve documentation (#639) * Improve docs * Remove paragraph * Text changes * Fix text for the BE error * Change tooltip (#650) * Change tooltip * Allows draft profiles to become expired (#636) * Allow draft profiles to expire * Move status method into risk profile class * Use existing method * Check for expiry in validate method * Remove unused variable * Build UI during package instead of install (#621) * Build UI during package * Fix local build * Install npm * Remove duplicate build message * Fix ESLint * Fix script * Modify scripts * Improve scripts * Fix copy command * Try installing package * Depend on package job * Add sudo * Add sudo * Troubleshoot * Fix workflow * Checkout source for prepare command * Built ui within a container * Mount src files for build instead of static copy in build image * Attempt to fix actions * Remove manual build container cleanup methods * undo failed attempts to fix actions * Fix path * Remove -it flag --------- Signed-off-by: J Boddey Co-authored-by: kurilova Co-authored-by: jhughesbiot * Feature/risk in selected (#654) * Adds risk to selected value * Adds risk to selected value --------- Co-authored-by: J Boddey * Show risk for each question in the Risk profile (#647) * Show risk for each question in the Risk profile * set top position to 0 --------- Co-authored-by: J Boddey * Show internet connection (#653) * MQTT show internet connection * remove unused method * Change tooltip for internet icon (#656) * Change tooltip for internet icon * Remove unused import' --------- Co-authored-by: Jacob Boddey --------- Signed-off-by: J Boddey Co-authored-by: J Boddey Co-authored-by: Sofia Kurilova * bug/modbus_constructor (#657) * Pin all required packages Update modbus constructor to prevent error Add full trace logging for general errors in tests * Fix pylint issue --------- Co-authored-by: Jacob Boddey * Use mqtt service instead of calling GET /status every 5 seconds. (#644) * Use mqtt service instead of calling GET /status every 5 seconds. * Adds tooltip (#638) Adds tooltip * Fix focus after profile delete - track by name (#640) Fix focus after profile delete - track by name * Update the requests dependency (#643) * Update requests dependency * Update requests dependency * Update dependency in TLS test * Update docker dependency --------- Signed-off-by: J Boddey * remove unused output * encode mqtt message to json * Revert "Expired profile (#619)" (#645) Prevent opening of Expired risk profile * Improve documentation (#639) * Improve docs * Remove paragraph * Text changes * Fix text for the BE error * Change tooltip (#650) * Change tooltip * Allows draft profiles to become expired (#636) * Allow draft profiles to expire * Move status method into risk profile class * Use existing method * Check for expiry in validate method * Remove unused variable * Build UI during package instead of install (#621) * Build UI during package * Fix local build * Install npm * Remove duplicate build message * Fix ESLint * Fix script * Modify scripts * Improve scripts * Fix copy command * Try installing package * Depend on package job * Add sudo * Add sudo * Troubleshoot * Fix workflow * Checkout source for prepare command * Built ui within a container * Mount src files for build instead of static copy in build image * Attempt to fix actions * Remove manual build container cleanup methods * undo failed attempts to fix actions * Fix path * Remove -it flag --------- Signed-off-by: J Boddey Co-authored-by: kurilova Co-authored-by: jhughesbiot * Feature/risk in selected (#654) * Adds risk to selected value * Adds risk to selected value --------- Co-authored-by: J Boddey * Show risk for each question in the Risk profile (#647) * Show risk for each question in the Risk profile * set top position to 0 --------- Co-authored-by: J Boddey * Use mqtt service instead of calling GET /status every 5 seconds. * Use mqtt service instead of calling GET /status every 5 seconds. * Use mqtt service instead of calling GET /status every 5 seconds. * pylint --------- Signed-off-by: J Boddey Co-authored-by: J Boddey Co-authored-by: Aliaksandr Nikitsin Co-authored-by: jhughesbiot * Adds test statuses (#661) * Ignore folders when loading certs (#660) * Remove scorecard schedule and bump version (#659) * Allow ICMP response to DHCP messages in DHCP snooping test (#608) * Allow ICMP response to DHCP messages * Bug/unit test runtime (#655) * Change base test module startup to allow setup script to run independent of module startup process Update connection_module to allow for unit testing Update unit test run script to use new process * enable all unit tests update google cert * Remove binary fix lines from docker files pylint updates * pylint updates --------- Co-authored-by: jhughesbiot * The risk profile saved with old format is shown improperly while loading based on a new format (#664) * Fill only fields that are present in profile * GAR : The alt text for the expired risk profile should be communicated on Enter key (#662) * Change Expired profile title on Enter; announce Expired profile title on Enter * Update wording of tls cipher results (#671) * Show error message if provided; show default message if no (#680) * Test install on supported operating systems (#675) * Test install on multiple versions * Update step names * Remove recommendations on error (#674) * Tests for API (#649) * changed the tests order in test_api.py * added tests for '/system/config' POST endpoint * added the tests for 'system/shutdown' endpoint * added the test for GET '/reports' endpoint, updated 'test_update_system_config_invalid_config' to return error 400 * Check for missing fields Signed-off-by: J Boddey * added tests for delete profile (404, 400), added tests for create and update profile (400), added test 'run_test_and_get_report' skipped due to blocking during testing phase * added a new json file in '/testing/api/' used in 400 error tests * added error handling if 'name' and 'questions' not in profile json * fixed pylint * Added tests when update is available and 500 status code for '/system/version', test for system/modules * added responses library in requirements.txt * fixed the requested changes in api.py * Renamed the load_profile method to load_json and changed the logic to allow to load any json based on file name and relative path, corrected the new lines issues * updated restore_config fixture to run after the test * added test for create/update profile (500 error) * fixed pylint * fixed spacing, removed get_report_one_report * added tests: 500 error for delete '/profiles', 500 error for 'profles/format', 400, 404, 409 for '/system/start' * modified the tests for 500 response * added new profile with missing 'answer' * removed the tests with mock response * Update NTP report (#666) * Update NTP report * cleanup imports * pylint updates * modified update profile for bad request * changed validate_profile_json: handling empty spaces in name and question, handle if 'risk' field missing if status is 'Valid' * updated the requested changes * Add further profile validation * Fix pylint issues * Fix profile tests * Move validation to session * Fix pylint issue Signed-off-by: J Boddey --------- Signed-off-by: J Boddey Co-authored-by: J Boddey Co-authored-by: jhughesbiot * Fix a delay in the internet connectivity check (#669) * Add a timeout to the command * move single-intf check to scheduler * add timeout arg to run_command * move jobs to constructor * fping for internet connection checking * Update internet connection when device is In Progress, Monitoring, Waiting for Device status (#677) * Update internet connection when device is In Progress, Monitoring, Waiting for Device status * check if interface physically connected * Revert "fping for internet connection checking" --------- Co-authored-by: Aliaksandr Nikitsin Co-authored-by: Sofia Kurilova * Check if device folder already exists (#678) * Check if device folder already exists * Fix error message * Fix pylint issue * Remove debug mqtt logs (#692) --------- Signed-off-by: J Boddey Signed-off-by: dependabot[bot] Co-authored-by: jhughesbiot Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sofia Kurilova Co-authored-by: Olga Mardvilko Co-authored-by: Volha Mardvilka Co-authored-by: jhughesbiot <50999916+jhughesbiot@users.noreply.github.com> Co-authored-by: Sofia Kurilova Co-authored-by: Aliaksandr Nikitsin Co-authored-by: Marius <86727846+MariusBaldovin@users.noreply.github.com> --- .github/workflows/package.yml | 68 +- .github/workflows/scorecard.yml | 6 +- .github/workflows/testing.yml | 24 +- .gitignore | 2 + README.md | 4 +- cmd/build | 19 +- cmd/build_ui | 37 + cmd/install | 18 +- cmd/package | 13 +- docs/README.md | 3 + docs/dev/README.md | 25 + docs/dev/code_quality.md | 16 + docs/network/README.md | 3 +- docs/network/add_new_service.md | 2 +- docs/test/README.md | 1 - docs/test/modules.md | 2 +- framework/python/src/api/api.py | 47 +- framework/python/src/common/mqtt.py | 62 + framework/python/src/common/risk_profile.py | 43 +- framework/python/src/common/session.py | 252 ++- framework/python/src/common/tasks.py | 78 + framework/python/src/common/util.py | 36 +- framework/python/src/core/testrun.py | 70 +- framework/python/src/net_orc/ip_control.py | 36 +- .../src/net_orc/network_orchestrator.py | 53 +- .../python/src/test_orc/test_orchestrator.py | 39 +- framework/requirements.txt | 12 +- make/DEBIAN/control | 2 +- modules/test/base/README.md | 7 + modules/test/base/bin/setup | 71 + modules/test/base/bin/start | 13 +- modules/test/base/bin/start_module | 146 +- modules/test/base/python/src/test_module.py | 13 +- .../test/conn/python/src/connection_module.py | 42 +- modules/test/conn/python/src/dhcp_util.py | 2 +- modules/test/dns/README.md | 3 +- modules/test/dns/conf/module_config.json | 6 + modules/test/dns/python/src/dns_module.py | 96 +- modules/test/ntp/python/src/ntp_module.py | 101 +- modules/test/protocol/bin/start_test_module | 104 +- modules/test/protocol/python/requirements.txt | 8 +- .../protocol/python/src/protocol_modbus.py | 2 +- .../services/python/src/services_module.py | 4 +- .../tls/bin/get_tls_client_connections.sh | 62 +- modules/test/tls/conf/module_config.json | 21 + modules/test/tls/python/requirements-test.txt | 1 + modules/test/tls/python/requirements.txt | 6 +- modules/test/tls/python/src/tls_util.py | 6 +- modules/test/tls/tls.Dockerfile | 14 +- modules/ui/angular.json | 8 +- .../build.sh => modules/ui/build.Dockerfile | 9 +- modules/ui/package-lock.json | 256 ++- modules/ui/package.json | 1 + modules/ui/src/app/app.component.html | 20 +- modules/ui/src/app/app.component.scss | 6 + modules/ui/src/app/app.component.spec.ts | 71 +- modules/ui/src/app/app.component.ts | 3 + modules/ui/src/app/app.module.ts | 10 + modules/ui/src/app/app.store.spec.ts | 84 +- modules/ui/src/app/app.store.ts | 75 +- .../download-report-zip.component.spec.ts | 8 +- .../download-report-zip.component.ts | 4 +- .../download-zip-modal.component.html | 93 +- .../download-zip-modal.component.scss | 23 +- .../download-zip-modal.component.spec.ts | 38 +- .../download-zip-modal.component.ts | 27 +- .../snack-bar/snack-bar.component.html | 5 +- .../app/components/wifi/wifi.component.html | 25 + .../app/components/wifi/wifi.component.scss | 40 + .../components/wifi/wifi.component.spec.ts | 100 ++ .../src/app/components/wifi/wifi.component.ts | 40 + .../interceptors/error.interceptor.spec.ts | 30 +- .../src/app/interceptors/error.interceptor.ts | 8 +- modules/ui/src/app/mocks/device.mock.ts | 4 +- modules/ui/src/app/mocks/profile.mock.ts | 54 + modules/ui/src/app/mocks/reports.mock.ts | 45 +- modules/ui/src/app/mocks/settings.mock.ts | 7 +- modules/ui/src/app/mocks/testrun.mock.ts | 2 +- modules/ui/src/app/mocks/topic.mock.ts | 5 + modules/ui/src/app/model/profile.ts | 6 +- modules/ui/src/app/model/setting.ts | 5 + modules/ui/src/app/model/testrun-status.ts | 21 +- modules/ui/src/app/model/topic.ts | 9 + .../certificates/certificates.store.spec.ts | 17 + .../pages/certificates/certificates.store.ts | 4 + .../device-form/device.validators.ts | 5 +- .../app/pages/devices/devices.component.html | 8 +- .../pages/devices/devices.component.spec.ts | 31 +- .../app/pages/devices/devices.component.ts | 27 +- .../app/pages/devices/devices.store.spec.ts | 3 + .../ui/src/app/pages/devices/devices.store.ts | 9 +- .../pages/reports/reports-routing.module.ts | 2 +- .../pages/reports/reports.component.spec.ts | 18 +- .../app/pages/reports/reports.component.ts | 173 ++ .../src/app/pages/reports/reports.module.ts | 2 +- .../app/pages/reports/reports.store.spec.ts | 100 +- .../ui/src/app/pages/reports/reports.store.ts | 91 +- .../src/app/pages/reports/reportscomponent.ts | 3 +- .../profile-form/profile-form.component.html | 15 + .../profile-form/profile-form.component.scss | 14 +- .../profile-form.component.spec.ts | 55 +- .../profile-form/profile-form.component.ts | 13 +- .../profile-form/profile.validators.ts | 8 +- .../profile-item/profile-item.component.html | 43 +- .../profile-item/profile-item.component.scss | 17 +- .../profile-item.component.spec.ts | 37 +- .../profile-item/profile-item.component.ts | 36 +- .../risk-assessment.component.html | 14 +- .../risk-assessment.component.scss | 2 +- .../risk-assessment.component.spec.ts | 78 +- .../risk-assessment.component.ts | 58 +- .../pages/settings/settings.component.html | 2 +- .../app/pages/settings/settings.store.spec.ts | 45 +- .../src/app/pages/settings/settings.store.ts | 78 +- .../testrun-initiate-form.component.spec.ts | 22 +- .../testrun-initiate-form.component.ts | 3 +- .../app/pages/testrun/testrun.component.html | 2 +- .../pages/testrun/testrun.component.spec.ts | 16 +- .../app/pages/testrun/testrun.component.ts | 16 +- .../app/pages/testrun/testrun.store.spec.ts | 3 + .../ui/src/app/pages/testrun/testrun.store.ts | 7 + .../services/test-run-mqtt.service.spec.ts | 102 ++ .../src/app/services/test-run-mqtt.service.ts | 38 + .../src/app/services/test-run.service.spec.ts | 56 +- .../ui/src/app/services/test-run.service.ts | 10 +- modules/ui/src/app/store/actions.ts | 25 +- modules/ui/src/app/store/effects.spec.ts | 135 +- modules/ui/src/app/store/effects.ts | 143 +- modules/ui/src/app/store/reducers.spec.ts | 71 +- modules/ui/src/app/store/reducers.ts | 24 + modules/ui/src/app/store/selectors.spec.ts | 28 + modules/ui/src/app/store/selectors.ts | 20 + modules/ui/src/app/store/state.ts | 16 +- modules/ui/ui.Dockerfile | 11 +- modules/ws/conf/mosquitto.conf | 22 + modules/ws/ws.Dockerfile | 4 + testing/api/profiles/new_profile.json | 54 + testing/api/profiles/new_profile_2.json | 56 + testing/api/profiles/updated_profile.json | 57 + testing/api/test_api.py | 1370 ++++++++++++---- testing/pylint/test_pylint | 28 +- testing/tests/test_tests.py | 2 +- testing/unit/conn/captures/monitor.pcap | Bin 0 -> 389089 bytes testing/unit/conn/captures/startup.pcap | Bin 0 -> 3404 bytes testing/unit/conn/conn_module_test.py | 32 +- testing/unit/dns/dns_module_test.py | 30 +- .../unit/dns/reports/dns_report_local.html | 2 +- testing/unit/framework/session_test.py | 57 + testing/unit/framework/util_test.py | 61 + testing/unit/ntp/ntp_module_test.py | 28 +- .../unit/ntp/reports/ntp_report_local.html | 1399 +---------------- .../ntp/reports/ntp_report_local_no_ntp.html | 1 + testing/unit/protocol/protocol_module_test.py | 1 - testing/unit/report/report_test.py | 49 + testing/unit/run.sh | 57 +- testing/unit/run_tests.sh | 69 - testing/unit/services/output/services.log | 6 - testing/unit/services/services_module_test.py | 31 +- testing/unit/tls/certs/_.google.com.crt | 153 +- testing/unit/tls/tls_module_test.py | 6 +- testing/unit/unit_test.Dockerfile | 47 - 161 files changed, 5329 insertions(+), 2892 deletions(-) create mode 100755 cmd/build_ui create mode 100644 docs/dev/README.md create mode 100644 docs/dev/code_quality.md create mode 100644 framework/python/src/common/mqtt.py create mode 100644 framework/python/src/common/tasks.py create mode 100644 modules/test/base/bin/setup create mode 100644 modules/test/tls/python/requirements-test.txt rename testing/unit/build.sh => modules/ui/build.Dockerfile (77%) create mode 100644 modules/ui/src/app/components/wifi/wifi.component.html create mode 100644 modules/ui/src/app/components/wifi/wifi.component.scss create mode 100644 modules/ui/src/app/components/wifi/wifi.component.spec.ts create mode 100644 modules/ui/src/app/components/wifi/wifi.component.ts create mode 100644 modules/ui/src/app/mocks/topic.mock.ts create mode 100644 modules/ui/src/app/model/topic.ts create mode 100644 modules/ui/src/app/pages/reports/reports.component.ts create mode 100644 modules/ui/src/app/services/test-run-mqtt.service.spec.ts create mode 100644 modules/ui/src/app/services/test-run-mqtt.service.ts create mode 100644 modules/ws/conf/mosquitto.conf create mode 100644 modules/ws/ws.Dockerfile create mode 100644 testing/api/profiles/new_profile.json create mode 100644 testing/api/profiles/new_profile_2.json create mode 100644 testing/api/profiles/updated_profile.json create mode 100644 testing/unit/conn/captures/monitor.pcap create mode 100644 testing/unit/conn/captures/startup.pcap create mode 100644 testing/unit/framework/session_test.py create mode 100644 testing/unit/framework/util_test.py delete mode 100644 testing/unit/run_tests.sh delete mode 100644 testing/unit/services/output/services.log delete mode 100644 testing/unit/unit_test.Dockerfile diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 0fdb8c379..8c4b5bcbe 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -7,9 +7,13 @@ on: push: branches: - 'dev' + - 'release/*' + +permissions: + contents: read jobs: - testrun_package: + create_package: permissions: {} name: Package runs-on: ubuntu-22.04 @@ -24,4 +28,64 @@ jobs: uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 with: name: testrun_package - path: testrun*.deb \ No newline at end of file + path: testrun*.deb + + install_package_20: + permissions: {} + needs: create_package + name: Install on Ubuntu 20.04 + runs-on: ubuntu-20.04 + timeout-minutes: 15 + steps: + - name: Checkout source + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Download package + uses: actions/download-artifact@v4 + with: + name: testrun_package + - name: Install dependencies + shell: bash {0} + run: sudo cmd/prepare + - name: Install package + shell: bash {0} + run: sudo apt install ./testrun*.deb + + install_package_22: + permissions: {} + needs: create_package + name: Install on Ubuntu 22.04 + runs-on: ubuntu-22.04 + timeout-minutes: 15 + steps: + - name: Checkout source + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Download package + uses: actions/download-artifact@v4 + with: + name: testrun_package + - name: Install dependencies + shell: bash {0} + run: sudo cmd/prepare + - name: Install package + shell: bash {0} + run: sudo apt install ./testrun*.deb + + install_package_24: + permissions: {} + needs: create_package + name: Install on Ubuntu 24.04 + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - name: Checkout source + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Download package + uses: actions/download-artifact@v4 + with: + name: testrun_package + - name: Install dependencies + shell: bash {0} + run: sudo cmd/prepare + - name: Install package + shell: bash {0} + run: sudo apt install ./testrun*.deb diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 12884c718..f0f89a631 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -7,10 +7,6 @@ on: # For Branch-Protection check. Only the default branch is supported. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection branch_protection_rule: - # To guarantee Maintained check is occasionally updated. See - # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained - schedule: - - cron: '20 6 * * 4' push: branches: [ "main" ] @@ -70,4 +66,4 @@ jobs: - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 with: - sarif_file: results.sarif + sarif_file: results.sarif \ No newline at end of file diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 0556a2189..d6deb1ab0 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -39,7 +39,7 @@ jobs: run: cmd/prepare - name: Install Testrun shell: bash {0} - run: TESTRUN_DIR=. cmd/install + run: cmd/install -l timeout-minutes: 30 - name: Run tests shell: bash {0} @@ -55,6 +55,28 @@ jobs: name: runtime_api_${{ github.run_id }} path: runtime.tgz + testrun_unit: + permissions: {} + name: Unit + runs-on: ubuntu-20.04 + timeout-minutes: 15 + steps: + - name: Checkout source + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Install dependencies + shell: bash {0} + run: cmd/prepare + - name: Install Testrun + shell: bash {0} + run: cmd/install -l + - name: Build Testrun + shell: bash {0} + run: cmd/build + timeout-minutes: 10 + - name: Run tests + shell: bash {0} + run: bash testing/unit/run.sh + pylint: permissions: {} name: Pylint diff --git a/.gitignore b/.gitignore index 82b6bbf64..92779dc04 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ build/ # Ignore generated files from unit tests testing/unit_test/temp/ +testing/unit/conn/output/ testing/unit/dns/output/ testing/unit/nmap/output/ testing/unit/ntp/output/ @@ -15,6 +16,7 @@ testing/unit/tls/output/ testing/unit/tls/tmp/ testing/unit/report/output/ testing/unit/risk_profile/output/ +testing/unit/services/output/ *.deb make/DEBIAN/postinst diff --git a/README.md b/README.md index 4a04e8885..23fd843ca 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ When manual testing or configuration changes are required, Testrun will provide - DHCP client - The device must be able to obtain an IP address via DHCP ## Get started ▶️ -Once you have met the hardware and software requirements, you can get started with Testrun by following the [Get started guide](docs/get_started.md). +Once you have met the hardware and software requirements, you can get started with Testrun by following the [Get started guide](docs/get_started.md). Further docs are available in the [docs directory](docs) ## Roadmap :chart_with_upwards_trend: Testrun will constantly evolve to further support end-users by automating device network behaviour against industry standards. For further information on upcoming features, check out the [Roadmap](docs/roadmap.pdf). @@ -59,7 +59,7 @@ We are proud of our tool and strive to provide an enjoyable experience for all o If the application has come across a problem at any point during setup or use, please raise an issue under the [issues tab](https://github.com/google/testrun/issues). Issue templates exist for both bug reports and feature requests. If neither of these are appropriate for your issue, raise a blank issue instead. ## Contributing :keyboard: -The contributing requirements can be found in [CONTRIBUTING.md](CONTRIBUTING.md). In short, checkout the [Google CLA](https://cla.developers.google.com/) site to get started. +The contributing requirements can be found in [CONTRIBUTING.md](CONTRIBUTING.md). In short, checkout the [Google CLA](https://cla.developers.google.com/) site to get started. After that, check out our [developer documentation](docs/dev/README.md). ## FAQ :raising_hand: 1) I have an issue whilst installing/upgrading Testrun, what do I do? diff --git a/cmd/build b/cmd/build index d15171f31..d3294a681 100755 --- a/cmd/build +++ b/cmd/build @@ -36,15 +36,28 @@ fi # Builds all docker images echo Building docker images -# Build user interface -echo Building user interface -if docker build -t test-run/ui -f modules/ui/ui.Dockerfile . ; then +# Check if UI has already been built (if -l was used during install) +if [ ! -d "modules/ui/dist" ]; then + cmd/build_ui +fi + +# Build UI image +if docker build -t testrun/ui -f modules/ui/ui.Dockerfile . ; then echo Successully built the user interface else echo An error occured whilst building the user interface exit 1 fi +# Build websockets server +echo Building websockets server +if docker build -t testrun/ws -f modules/ws/ws.Dockerfile . ; then + echo Successully built the web sockets server +else + echo An error occured whilst building the websockets server + exit 1 +fi + # Build network modules echo Building network modules mkdir -p build/network diff --git a/cmd/build_ui b/cmd/build_ui new file mode 100755 index 000000000..afb0d8827 --- /dev/null +++ b/cmd/build_ui @@ -0,0 +1,37 @@ +#!/bin/bash -e + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Build the UI +echo Building the ui builder + +# Build UI builder image +if docker build -t testrun/build-ui -f modules/ui/build.Dockerfile . ; then + echo Successully built the ui builder +else + echo An error occured whilst building the ui builder + exit 1 +fi + +# Check that the container is not already running +docker kill tr-ui-build 2> /dev/null || true + +echo "Building the user interface" + +# Start build container and build the ui dist +docker run --rm -v $PWD/modules/ui:/modules/ui testrun/build-ui /bin/sh -c "npm install && npm run build" + +# Kill the container (Should not be running anymore) +docker kill tr-ui-build 2> /dev/null || true diff --git a/cmd/install b/cmd/install index 53d12b324..c350a969f 100755 --- a/cmd/install +++ b/cmd/install @@ -20,15 +20,29 @@ echo Installing application dependencies while getopts ":l" option; do case $option in l) # Install Testrun in local directory - TESTRUN_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")"/.. && pwd) + TESTRUN_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")"/.. && pwd) esac done # Check if TESTRUN_DIR has been set, otherwise install in /usr/local/testrun if [[ -z "${TESTRUN_DIR}" ]]; then TESTRUN_DIR=/usr/local/testrun + + # Check that user is sudo + if [[ "$EUID" -ne 0 ]]; then + echo "Installing Testrun in the default location requires sudo. Run using sudo cmd/install" + exit 1 + fi + else TESTRUN_DIR="${TESTRUN_DIR}" + + # Check that user is in docker group + if ! (id -nGz "$USER" | grep -qzxF "docker"); then + echo User is not in docker group. Follow https://docs.docker.com/engine/install/linux-postinstall/ to finish setting up docker. + exit 1 + fi + fi echo Installing Testrun at $TESTRUN_DIR @@ -51,7 +65,7 @@ cp -n local/system.json.example local/system.json deactivate # Build docker images -sudo cmd/build +cmd/build # Create local folders mkdir -p local/devices diff --git a/cmd/package b/cmd/package index fc418ab05..719258a83 100755 --- a/cmd/package +++ b/cmd/package @@ -16,6 +16,12 @@ # Creates a package for Testrun +# Check that user is not root +if [[ "$EUID" == 0 ]]; then + echo "Must not run as root. Use cmd/package as regular user" + exit 1 +fi + MAKE_SRC_DIR=make MAKE_CONTROL_DIR=make/DEBIAN/control @@ -25,10 +31,10 @@ version=$(grep -R "Version: " $MAKE_CONTROL_DIR | awk '{print $2}') # Replace invalid characters version="${version//./_}" -# Delete existing make files -rm -rf $MAKE_SRC_DIR/usr +echo Building package for testrun v${version} # Delete existing make files +echo Cleaning up previous build files rm -rf $MAKE_SRC_DIR/usr # Copy testrun script to /bin @@ -60,6 +66,9 @@ mkdir -p $MAKE_SRC_DIR/usr/local/testrun/local/risk_profiles mkdir -p local/root_certs cp -r local/root_certs $MAKE_SRC_DIR/usr/local/testrun/local/ +# Build the UI +cmd/build_ui + # Copy framework and modules into testrun folder cp -r {framework,modules} $MAKE_SRC_DIR/usr/local/testrun diff --git a/docs/README.md b/docs/README.md index 96eb32223..5f055dbb9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -16,3 +16,6 @@ - [Running on a virtual machine](virtual_machine.md) - [Accessibility](ui/accessibility.mp4) - [Roadmap](roadmap.pdf) + +## Something missing? +If you feel there is some documentation that you would find useful, or have found an issue with existing documentation, please raise an issue on GitHub by navigating [here](https://github.com/google/testrun/issues/new/choose) \ No newline at end of file diff --git a/docs/dev/README.md b/docs/dev/README.md new file mode 100644 index 000000000..f11b1b092 --- /dev/null +++ b/docs/dev/README.md @@ -0,0 +1,25 @@ +Testrun logo + +## Developer docs + +## Table of Contents +1) General guidelines (this page) +2) [Code quality](code_quality.md) + +## General guidelines +As an open source project, we absolutely encourage contributions from the community to help Testrun remain an expanding but stable product. However, before contributing there are a number of things to take into consideration. + +1) [Sign the Google CLA](https://cla.developers.google.com/): Whether you are an individual or contributing on behalf of your organisation, you must be covered by a Google CLA. + +2) Determine the scope of your contribution + + - Your contribution is more likely to be accepted if fewer files are changed (keep it simple) + - Are you going to be fixing a bug, dependency issue or a new framework capability? Whatever it is, ensure your pull request fixes or changes just one thing. + +3) Get in touch to discuss whether your proposed changes are likely to be accepted + + - It is best to get the opinion from the core maintainers whether your proposed changes meet our objectives and align with Testrun principles. + +4) Fork Testrun and get developing + + - We aim to provide thorough and easy to ready developer documentation to help you contribute successfully. \ No newline at end of file diff --git a/docs/dev/code_quality.md b/docs/dev/code_quality.md new file mode 100644 index 000000000..47eabcf95 --- /dev/null +++ b/docs/dev/code_quality.md @@ -0,0 +1,16 @@ +Testrun logo + +## Code quality + +Whilst developing code for Testrun, there are some style guides that you should follow. + + - Python: https://google.github.io/styleguide/pyguide.html + - Angular: https://google.github.io/styleguide/angularjs-google-style.html + - Shell: https://google.github.io/styleguide/shellguide.html + - HTML/CSS: https://google.github.io/styleguide/htmlcssguide.html + - JSON: https://google.github.io/styleguide/jsoncstyleguide.xml + - Markdown: https://google.github.io/styleguide/docguide/style.html + +### Automated actions + +The current code base has been able to achieve 0 code lint issues. To maintain this, all lint checks are enforced on pull requests to dev and main. Please ensure that these lint checks are passing before marking your pull requests as 'Ready for review'. \ No newline at end of file diff --git a/docs/network/README.md b/docs/network/README.md index b5536c30c..0f97ecd7b 100644 --- a/docs/network/README.md +++ b/docs/network/README.md @@ -1,10 +1,9 @@ Testrun logo - ## Network Overview ## Table of Contents -1) Network Overview (this page) +1) Network overview (this page) 2) [How to identify network interfaces](identify_interfaces.md) 3) [Addresses](addresses.md) 4) [Add a new network service](add_new_service.md) diff --git a/docs/network/add_new_service.md b/docs/network/add_new_service.md index 7a07e43be..b3fa22514 100644 --- a/docs/network/add_new_service.md +++ b/docs/network/add_new_service.md @@ -65,7 +65,7 @@ COPY $MODULE_DIR/bin /testrun/bin # Copy over all python files COPY $MODULE_DIR/python /testrun/python -# Do not specify a CMD or Entrypoint as Test Run will automatically start your service as required +# Do not specify a CMD or Entrypoint as Testrun will automatically start your service as required ``` ### Example of start_network_service script diff --git a/docs/test/README.md b/docs/test/README.md index 19aa691d8..3163b4c84 100644 --- a/docs/test/README.md +++ b/docs/test/README.md @@ -2,7 +2,6 @@ ## Testing - The test requirements that are investigated by Testrun can be found in the [test modules documentation](/docs/test/modules.md). To understand the testing results, various definitions of test results and requirements are specified in the [statuses documentation](/docs/test/statuses.md). \ No newline at end of file diff --git a/docs/test/modules.md b/docs/test/modules.md index 7c5851ba4..2fe5983b1 100644 --- a/docs/test/modules.md +++ b/docs/test/modules.md @@ -10,7 +10,7 @@ Testrun provides some pre-built test modules for you to use when testing your ow | Baseline | A sample test module | [Baseline module](/modules/test/baseline/README.md) | | Connection | Verify IP and DHCP based behavior | [Connection module](/modules/test/conn/README.md) | | DNS | Verify DNS functionality | [DNS module](/modules/test/dns/README.md) | -| NMAP | Ensure unsecure services are disabled | [NMAP module](/modules/test/nmap/README.md) | +| Services | Ensure unsecure services are disabled | [Services module](/modules/test/services/README.md) | | NTP | Verify NTP functionality | [NTP module](/modules/test/ntp/README.md) | | Protocol | Inspect BMS protocol implementation | [Protocol Module](/modules/test/protocol/README.md) | | TLS | Determine TLS client and server behavior | [TLS module](/modules/test/tls/README.md) | diff --git a/framework/python/src/api/api.py b/framework/python/src/api/api.py index aed663ab8..e8e87465d 100644 --- a/framework/python/src/api/api.py +++ b/framework/python/src/api/api.py @@ -26,7 +26,7 @@ import uvicorn from urllib.parse import urlparse -from common import logger +from common import logger, tasks from common.device import Device LOGGER = logger.get_logger("api") @@ -114,7 +114,10 @@ def __init__(self, test_run): # Allow all origins to access the API origins = ["*"] - self._app = FastAPI() + # Scheduler for background periodic tasks + self._scheduler = tasks.PeriodicTasks(self._test_run) + + self._app = FastAPI(lifespan=self._scheduler.start) self._app.include_router(self._router) self._app.add_middleware( CORSMiddleware, @@ -165,7 +168,19 @@ async def post_sys_config(self, request: Request, response: Response): try: config = (await request.body()).decode("UTF-8") config_json = json.loads(config) + + # Validate req fields + if ("network" not in config_json or + "device_intf" not in config_json.get("network") or + "internet_intf" not in config_json.get("network") or + "log_level" not in config_json): + response.status_code = status.HTTP_400_BAD_REQUEST + return self._generate_msg( + False, + "Configuration is missing required fields") + self._session.set_config(config_json) + # Catch JSON Decode error etc except JSONDecodeError: response.status_code = status.HTTP_400_BAD_REQUEST @@ -231,7 +246,15 @@ async def start_test_run(self, request: Request, response: Response): False, "Configured interfaces are not " + "ready for use. Ensure required interfaces " + "are connected.") - device.test_modules = body_json["device"]["test_modules"] + # UI doesn't send individual test configs so we need to + # merge these manually until the UI is updated to handle + # the full config file + for module_name, module_config in device.test_modules.items(): + # Check if the module exists in UI test modules + if module_name in body_json["device"]["test_modules"]: + # Merge the enabled state + module_config["enabled"] = body_json[ + "device"]["test_modules"][module_name]["enabled"] LOGGER.info("Starting Testrun with device target " + f"{device.manufacturer} {device.model} with " + @@ -464,6 +487,19 @@ async def save_device(self, request: Request, response: Response): device_json.get(DEVICE_MODEL_KEY) ) + # Check if device folder exists + device_folder = os.path.join(self._test_run.get_root_dir(), + DEVICES_PATH, + device_json.get(DEVICE_MANUFACTURER_KEY) + + " " + + device_json.get(DEVICE_MODEL_KEY)) + + if os.path.exists(device_folder): + response.status_code = status.HTTP_409_CONFLICT + return self._generate_msg( + False, "A folder with that name already exists, " \ + "please rename the device or folder") + if device is None: # Create new device @@ -679,6 +715,11 @@ async def update_profile(self, request: Request, response: Response): response.status_code = status.HTTP_400_BAD_REQUEST return self._generate_msg(False, "Invalid request received") + # Validate json profile + if not self.get_session().validate_profile_json(req_json): + response.status_code = status.HTTP_400_BAD_REQUEST + return self._generate_msg(False, "Invalid request received") + profile_name = req_json.get("name") # Check if profile exists diff --git a/framework/python/src/common/mqtt.py b/framework/python/src/common/mqtt.py new file mode 100644 index 000000000..c58d24d3f --- /dev/null +++ b/framework/python/src/common/mqtt.py @@ -0,0 +1,62 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""MQTT client""" +import json +import typing as t +import paho.mqtt.client as mqtt_client +from common import logger + +LOGGER = logger.get_logger("mqtt") +WEBSOCKETS_HOST = "localhost" +WEBSOCKETS_PORT = 1883 + +class MQTTException(Exception): + def __init__(self, message: str) -> None: + super().__init__(message) + + +class MQTT: + """ MQTT client class + """ + def __init__(self) -> None: + self._host = WEBSOCKETS_HOST + self._client = mqtt_client.Client(mqtt_client.CallbackAPIVersion.VERSION2) + LOGGER.setLevel(logger.logging.INFO) + self._client.enable_logger(LOGGER) + + def _connect(self): + """Establish connection to Mosquitto server + + Raises: + MQTTException: Raises exception on connection error + """ + if not self._client.is_connected(): + try: + self._client.connect(self._host, WEBSOCKETS_PORT, 60) + except (ValueError, ConnectionRefusedError) as e: + LOGGER.error("Can't connect to host") + raise MQTTException("Connection to the Mosquitto server failed") from e + + def send_message(self, topic: str, message: t.Union[str, dict]) -> None: + """Send message to specific topic + + Args: + topic (str): mqtt topic + message (t.Union[str, dict]): message + """ + self._connect() + if isinstance(message, dict): + message = json.dumps(message) + self._client.publish(topic, str(message)) diff --git a/framework/python/src/common/risk_profile.py b/framework/python/src/common/risk_profile.py index 6afb229ac..f50dffdde 100644 --- a/framework/python/src/common/risk_profile.py +++ b/framework/python/src/common/risk_profile.py @@ -96,11 +96,11 @@ def get_file_path(self): self.name + '.json') def _validate(self, profile_json, profile_format): - if self._valid(profile_json, profile_format): - if self._expired(): - self.status = 'Expired' + if self._expired(): + self.status = 'Expired' + elif self._valid(profile_json, profile_format): # User only wants to save a draft - elif 'status' in profile_json and profile_json['status'] == 'Draft': + if 'status' in profile_json and profile_json['status'] == 'Draft': self.status = 'Draft' else: self.status = 'Valid' @@ -409,6 +409,14 @@ def _generate_risk_questions(self): content += '' + # Question risk label + if 'risk' in question: + if question['risk'] == 'High': + content += '
HIGH RISK
' + elif question['risk'] == 'Limited': + content += '''
+ LIMITED RISK
''' + content += '''''' index += 1 @@ -635,6 +643,33 @@ def _generate_css(self): ul { margin-top: 0; } + + .risk-label{ + position: absolute; + top: 0px; + right: 0px; + width: 52px; + height: 16px; + font-family: 'Google Sans', sans-serif; + font-size: 8px; + font-weight: 500; + line-height: 16px; + letter-spacing: 0.64px; + text-align: center; + font-weight: bold; + border-radius: 3px; + } + + .risk-label-high{ + background-color: #FCE8E6; + color: #C5221F; + } + + .risk-label-limited{ + width: 65px; + background-color:#E4F7FB; + color: #007B83; + } ''' def to_pdf(self, device): diff --git a/framework/python/src/common/session.py b/framework/python/src/common/session.py index f555a9732..940fbe8f0 100644 --- a/framework/python/src/common/session.py +++ b/framework/python/src/common/session.py @@ -17,8 +17,10 @@ import pytz import json import os -from common import util, logger +from fastapi.encoders import jsonable_encoder +from common import util, logger, mqtt from common.risk_profile import RiskProfile +from net_orc.ip_control import IPControl # Certificate dependencies from cryptography import x509 @@ -36,7 +38,7 @@ MAX_DEVICE_REPORTS_KEY = 'max_device_reports' CERTS_PATH = 'local/root_certs' CONFIG_FILE_PATH = 'local/system.json' -SECONDS_IN_YEAR = 31536000 +STATUS_TOPIC = 'status' PROFILE_FORMAT_PATH = 'resources/risk_assessment.json' PROFILES_DIR = 'local/risk_profiles' @@ -44,8 +46,36 @@ LOGGER = logger.get_logger('session') +def session_tracker(method): + """Session changes tracker.""" + def wrapper(self, *args, **kwargs): + + result = method(self, *args, **kwargs) + + if self.get_status() != 'Idle': + self.get_mqtt_client().send_message( + STATUS_TOPIC, + jsonable_encoder(self.to_json()) + ) + + return result + return wrapper + +def apply_session_tracker(cls): + """Applies tracker decorator to class methods""" + for attr in dir(cls): + if (callable(getattr(cls, attr)) + and not attr.startswith('_') + and not attr.startswith('get') + and not attr == 'to_json' + ): + setattr(cls, attr, session_tracker(getattr(cls, attr))) + return cls + + +@apply_session_tracker class TestrunSession(): - """Represents the current session of Test Run.""" + """Represents the current session of Testrun.""" def __init__(self, root_dir): self._root_dir = root_dir @@ -93,6 +123,8 @@ def __init__(self, root_dir): self._config_file = os.path.join(root_dir, CONFIG_FILE_PATH) self._config = self._get_default_config() + # System network interfaces + self._ifaces = {} # Loading methods self._load_version() self._load_config() @@ -107,6 +139,9 @@ def __init__(self, root_dir): self._timezone = tz[0] LOGGER.debug(f'System timezone is {self._timezone}') + # MQTT client + self._mqtt_client = mqtt.MQTT() + def start(self): self.reset() self._status = 'Waiting for Device' @@ -332,6 +367,12 @@ def add_test_result(self, result): result.result = 'In Progress' self._results.append(result) + def set_test_result_error(self, result): + """Set test result error""" + result.result = 'Error' + result.recommendations = None + self._results.append(result) + def add_module_report(self, module_report): self._module_reports.append(module_report) @@ -399,17 +440,31 @@ def _load_profiles(self): try: for risk_profile_file in os.listdir( os.path.join(self._root_dir, PROFILES_DIR)): + LOGGER.debug(f'Discovered profile {risk_profile_file}') + # Open the risk profile file with open(os.path.join(self._root_dir, PROFILES_DIR, risk_profile_file), encoding='utf-8') as f: + + # Parse risk profile json json_data = json.load(f) + + # Validate profile JSON + if not self.validate_profile_json(json_data): + LOGGER.error('Profile failed validation') + continue + + # Instantiate a new risk profile risk_profile = RiskProfile() + + # Pass JSON to populate risk profile risk_profile.load( profile_json=json_data, profile_format=self._profile_format ) - risk_profile.status = self.check_profile_status(risk_profile) + + # Add risk profile to session self._profiles.append(risk_profile) except Exception as e: @@ -428,25 +483,6 @@ def get_profile(self, name): return profile return None - def validate_profile(self, profile_json): - - # Check name field is present - if 'name' not in profile_json: - return False - - # Check questions field is present - if 'questions' not in profile_json: - return False - - # Check all questions are present - for format_q in self.get_profiles_format(): - if self._get_profile_question(profile_json, - format_q.get('question')) is None: - LOGGER.error('Missing question: ' + format_q.get('question')) - return False - - return True - def _get_profile_question(self, profile_json, question): for q in profile_json.get('questions'): @@ -455,7 +491,14 @@ def _get_profile_question(self, profile_json, question): return None + def get_profile_format_question(self, question): + for q in self.get_profiles_format(): + if q.get('question') == question: + return q + def update_profile(self, profile_json): + """Update the risk profile with the provided JSON. + The content has already been validated in the API""" profile_name = profile_json['name'] @@ -463,39 +506,8 @@ def update_profile(self, profile_json): profile_json['version'] = self.get_version() profile_json['created'] = datetime.datetime.now().strftime('%Y-%m-%d') - if 'status' in profile_json and profile_json.get('status') == 'Valid': - # Attempting to submit a risk profile, we need to check it - - # Check all questions have been answered - all_questions_answered = True - - for question in self.get_profiles_format(): - - # Check question is present - profile_question = self._get_profile_question(profile_json, - question.get('question')) - - if profile_question is not None: - - # Check answer is present - if 'answer' not in profile_question: - LOGGER.error('Missing answer for question: ' + - question.get('question')) - all_questions_answered = False - - else: - LOGGER.error('Missing question: ' + question.get('question')) - all_questions_answered = False - - if not all_questions_answered: - LOGGER.error('Not all questions answered') - return None - - else: - profile_json['status'] = 'Draft' - + # Check if profile already exists risk_profile = self.get_profile(profile_name) - if risk_profile is None: # Create a new risk profile @@ -524,19 +536,105 @@ def update_profile(self, profile_json): return risk_profile - def check_profile_status(self, profile): + def validate_profile_json(self, profile_json): + """Validate properties in profile update requests""" + + # Get the status field + valid = False + if 'status' in profile_json and profile_json.get('status') == 'Valid': + valid = True + + # Check if 'name' exists in profile + if 'name' not in profile_json: + LOGGER.error('Missing "name" in profile') + return False + + # Check if 'name' field not empty + elif len(profile_json.get('name').strip()) == 0: + LOGGER.error('Name field left empty') + return False + + # Error handling if 'questions' not in request + if 'questions' not in profile_json and valid: + LOGGER.error('Missing "questions" field in profile') + return False + + # Validating the questions section + for question in profile_json.get('questions'): + + # Check if the question field is present + if 'question' not in question: + LOGGER.error('The "question" field is missing') + return False + + # Check if 'question' field not empty + elif len(question.get('question').strip()) == 0: + LOGGER.error('A question is missing from "question" field') + return False + + # Check if question is a recognized question + format_q = self.get_profile_format_question( + question.get('question')) + + if format_q is None: + LOGGER.error(f'Unrecognized question: {question.get("question")}') + return False + + # Error handling if 'answer' is missing + if 'answer' not in question and valid: + LOGGER.error('The answer field is missing') + return False + + # If answer is present, check the validation rules + else: + + # Extract the answer out of the profile + answer = question.get('answer') + + # Get the validation rules + field_type = format_q.get('type') - if profile.status == 'Valid': + # Check if type is string or single select, answer should be a string + if ((field_type in ['string', 'select']) + and not isinstance(answer, str)): + LOGGER.error(f'''Answer for question \ +{question.get('question')} is incorrect data type''') + return False - # Check expiry - created_date = profile.created.timestamp() + # Check if type is select, answer must be from list + if field_type == 'select' and valid: + possible_answers = format_q.get('options') + if answer not in possible_answers: + LOGGER.error(f'''Answer for question \ +{question.get('question')} is not valid''') + return False - today = datetime.datetime.now().timestamp() + # Validate select multiple field types + if field_type == 'select-multiple': - if created_date < (today - SECONDS_IN_YEAR): - profile.status = 'Expired' + if not isinstance(answer, list): + LOGGER.error(f'''Answer for question \ +{question.get('question')} is incorrect data type''') + return False - return profile.status + question_options_len = len(format_q.get('options')) + + # We know it is a list, now check the indexes + for index in answer: + + # Check if the index is an integer + if not isinstance(index, int): + LOGGER.error(f'''Answer for question \ +{question.get('question')} is incorrect data type''') + return False + + # Check if index is 0 or above and less than the num of options + if index < 0 or index >= question_options_len: + LOGGER.error(f'''Invalid index provided as answer for \ +question {question.get('question')}''') + return False + + return True def delete_profile(self, profile): @@ -565,6 +663,7 @@ def reset(self): self._results = [] self._started = None self._finished = None + self._ifaces = IPControl.get_sys_interfaces() def to_json(self): @@ -650,6 +749,11 @@ def load_certs(self): self._certs = [] for cert_file in os.listdir(CERTS_PATH): + + # Ignore directories + if os.path.isdir(os.path.join(CERTS_PATH, cert_file)): + continue + LOGGER.debug(f'Loading certificate {cert_file}') try: @@ -712,3 +816,25 @@ def delete_cert(self, filename): def get_certs(self): return self._certs + + def detect_network_adapters_change(self) -> dict: + adapters = {} + ifaces_new = IPControl.get_sys_interfaces() + + # Difference between stored and newly received network interfaces + diff = util.diff_dicts(self._ifaces, ifaces_new) + if diff: + if 'items_added' in diff: + adapters['adapters_added'] = diff['items_added'] + if 'items_removed' in diff: + adapters['adapters_removed'] = diff['items_removed'] + # Save new network interfaces to session + LOGGER.debug(f'Network adapters change detected: {adapters}') + self._ifaces = ifaces_new + return adapters + + def get_mqtt_client(self): + return self._mqtt_client + + def get_ifaces(self): + return self._ifaces diff --git a/framework/python/src/common/tasks.py b/framework/python/src/common/tasks.py new file mode 100644 index 000000000..5da0b40c9 --- /dev/null +++ b/framework/python/src/common/tasks.py @@ -0,0 +1,78 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Periodic background tasks""" + +from contextlib import asynccontextmanager +import datetime +import logging + +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from fastapi import FastAPI + +from common import logger + +# Check adapters period seconds +# Check adapters period seconds +CHECK_NETWORK_ADAPTERS_PERIOD = 5 +CHECK_INTERNET_PERIOD = 2 +INTERNET_CONNECTION_TOPIC = 'events/internet' +NETWORK_ADAPTERS_TOPIC = 'events/adapter' + +LOGGER = logger.get_logger('tasks') + + +class PeriodicTasks: + """Background periodic tasks + """ + def __init__( + self, testrun_obj, + ) -> None: + self._testrun = testrun_obj + self._mqtt_client = self._testrun.get_mqtt_client() + local_tz = datetime.datetime.now().astimezone().tzinfo + self._scheduler = AsyncIOScheduler(timezone=local_tz) + # Prevent scheduler warnings + self._scheduler._logger.setLevel(logging.ERROR) + + self.adapters_checker_job = self._scheduler.add_job( + func=self._testrun.get_net_orc().network_adapters_checker, + kwargs={ + 'mqtt_client': self._mqtt_client, + 'topic': NETWORK_ADAPTERS_TOPIC + }, + trigger='interval', + seconds=CHECK_NETWORK_ADAPTERS_PERIOD, + ) + # add internet connection cheking job only in single-intf mode + if 'single_intf' not in self._testrun.get_session().get_runtime_params(): + self.internet_shecker = self._scheduler.add_job( + func=self._testrun.get_net_orc().internet_conn_checker, + kwargs={ + 'mqtt_client': self._mqtt_client, + 'topic': INTERNET_CONNECTION_TOPIC + }, + trigger='interval', + seconds=CHECK_INTERNET_PERIOD, + ) + + @asynccontextmanager + async def start(self, app: FastAPI): # pylint: disable=unused-argument + """Start background tasks + + Args: + app (FastAPI): app instance + """ + # Job that checks for changes in network adapters + self._scheduler.start() + yield diff --git a/framework/python/src/common/util.py b/framework/python/src/common/util.py index 096aaf4df..7c31631fb 100644 --- a/framework/python/src/common/util.py +++ b/framework/python/src/common/util.py @@ -17,13 +17,14 @@ import os import subprocess import shlex -from common import logger +import typing as t import netifaces +from common import logger LOGGER = logger.get_logger('util') -def run_command(cmd, output=True): +def run_command(cmd, output=True, timeout=None): """Runs a process at the os level By default, returns the standard output and error output If the caller sets optional output parameter to False, @@ -35,7 +36,7 @@ def run_command(cmd, output=True): with subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE) as process: - stdout, stderr = process.communicate() + stdout, stderr = process.communicate(timeout) if process.returncode != 0 and output: err_msg = f'{stderr.strip()}. Code: {process.returncode}' @@ -113,3 +114,32 @@ def get_module_display_name(search): return module[1] return 'Unknown' + + +def diff_dicts(d1: t.Dict[t.Any, t.Any], d2: t.Dict[t.Any, t.Any]) -> t.Dict: + """Compares two dictionaries by keys + + Args: + d1 (t.Dict[t.Any, t.Any]): first dict to compare + d2 (t.Dict[t.Any, t.Any]): second dict to compare + + Returns: + t.Dict[t.Any, t.Any]: Returns an empty dictionary + if the compared dictionaries are equal, + otherwise returns a dictionary that contains + the removed items(if available) + and the added items(if available). + """ + diff = {} + if d1 != d2: + s1 = set(d1) + s2 = set(d2) + keys_removed = s1 - s2 + keys_added = s2 - s1 + items_removed = {k:d1[k] for k in keys_removed} + items_added = {k:d2[k] for k in keys_added} + if items_removed: + diff['items_removed'] = items_removed + if items_added: + diff['items_added'] = items_added + return diff diff --git a/framework/python/src/core/testrun.py b/framework/python/src/core/testrun.py index 5b43cfd65..dccde6a35 100644 --- a/framework/python/src/core/testrun.py +++ b/framework/python/src/core/testrun.py @@ -15,7 +15,7 @@ """The overall control of the Test Run application. This file provides the integration between all of the -Test Run components, such as net_orc, test_orc and test_ui. +Testrun components, such as net_orc, test_orc and test_ui. Run using the provided command scripts in the cmd folder. E.g sudo cmd/start @@ -27,7 +27,7 @@ import signal import sys import time -from common import logger, util +from common import logger, util, mqtt from common.device import Device from common.session import TestrunSession from common.testreport import TestReport @@ -81,7 +81,9 @@ def __init__(self, self._net_only = net_only self._single_intf = single_intf - self._no_ui = no_ui + # Network only option only works if UI is also + # disbled so need to set no_ui if net_only is selected + self._no_ui = no_ui or net_only # Catch any exit signals self._register_exits() @@ -109,6 +111,12 @@ def __init__(self, # Load test modules self._test_orc.start() + # Start websockets server + self.start_ws() + + # Init MQTT client + self._mqtt_client = mqtt.MQTT() + if self._no_ui: # Check Testrun is able to start @@ -216,7 +224,14 @@ def _load_test_reports(self, device): 'test', device.mac_addr.replace(':',''), 'report.json') - + + if not os.path.isfile(report_json_file_path): + # Revert to pre 1.3 file path + report_json_file_path = os.path.join( + reports_folder, + report_folder, + 'report.json') + if not os.path.isfile(report_json_file_path): # Revert to pre 1.3 file path report_json_file_path = os.path.join( @@ -369,6 +384,7 @@ def shutdown(self): LOGGER.info('Shutting down Testrun') self.stop() self._stop_ui() + self._stop_ws() def _exit_handler(self, signum, arg): # pylint: disable=unused-argument LOGGER.debug('Exit signal received: ' + str(signum)) @@ -385,6 +401,9 @@ def _get_config_abs(self, config_file=None): # Expand the config file to absolute pathing return os.path.abspath(config_file) + def get_root_dir(self): + return root_dir + def get_config_file(self): return self._get_config_abs() @@ -406,6 +425,9 @@ def _stop_network(self, kill=True): def _stop_tests(self): self._test_orc.stop() + def get_mqtt_client(self): + return self._mqtt_client + def get_device(self, mac_addr): """Returns a loaded device object from the device mac address.""" for device in self.get_session().get_device_repository(): @@ -463,7 +485,7 @@ def start_ui(self): try: client.containers.run( - image='test-run/ui', + image='testrun/ui', auto_remove=True, name='tr-ui', hostname='testrun.io', @@ -489,4 +511,40 @@ def _stop_ui(self): if container is not None: container.kill() except docker.errors.NotFound: - return + pass + + + def start_ws(self): + + self._stop_ws() + + LOGGER.info('Starting WS server') + + client = docker.from_env() + + try: + client.containers.run( + image='testrun/ws', + auto_remove=True, + name='tr-ws', + detach=True, + ports={ + '9001': 9001, + '1883': 1883 + } + ) + except ImageNotFound as ie: + LOGGER.error('An error occured whilst starting the websockets server. ' + + 'Please investigate and try again.') + LOGGER.error(ie) + sys.exit(1) + + def _stop_ws(self): + LOGGER.info('Stopping websockets server') + client = docker.from_env() + try: + container = client.containers.get('tr-ws') + if container is not None: + container.kill() + except docker.errors.NotFound: + pass diff --git a/framework/python/src/net_orc/ip_control.py b/framework/python/src/net_orc/ip_control.py index 506b23a95..04686f0cd 100644 --- a/framework/python/src/net_orc/ip_control.py +++ b/framework/python/src/net_orc/ip_control.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. """IP Control Module""" +import psutil +import typing as t from common import logger from common import util import re @@ -43,10 +45,7 @@ def add_namespace(self, namespace): def check_interface_status(self, interface_name): output = util.run_command(cmd=f'ip link show {interface_name}', output=True) - if 'state DOWN ' in output[0]: - return False - else: - return True + return 'state UP ' in output[0] def delete_link(self, interface_name): """Delete an ip link""" @@ -99,7 +98,7 @@ def get_iface_port_stats(self, iface): def get_namespaces(self): result = util.run_command('ip netns list') - #Strip ID's from the namespace results + # Strip ID's from the namespace results namespaces = re.findall(r'(\S+)(?:\s+\(id: \d+\))?', result[0]) return namespaces @@ -237,3 +236,30 @@ def configure_container_interface(self, LOGGER.error(f'Failed to set interface up {namespace_intf}') return False return True + + def ping_via_gateway(self, host): + """Ping the host trough the gateway container""" + command = f'timeout 3 docker exec tr-ct-gateway ping -W 1 -c 1 {host}' + output = util.run_command(command) + if '0% packet loss' in output[0]: + return True + return False + + @staticmethod + def get_sys_interfaces() -> t.Dict[str, t.Dict[str, str]]: + """ Retrieves all Ethernet network interfaces from the host system + Returns: + t.Dict[str, str] + """ + addrs = psutil.net_if_addrs() + ifaces = {} + + for key in addrs: + nic = addrs[key] + # Ignore any interfaces that are not ethernet + if not (key.startswith('en') or key.startswith('eth')): + continue + + ifaces[key] = nic[0].address + + return ifaces diff --git a/framework/python/src/net_orc/network_orchestrator.py b/framework/python/src/net_orc/network_orchestrator.py index f20093a28..a94bca89b 100644 --- a/framework/python/src/net_orc/network_orchestrator.py +++ b/framework/python/src/net_orc/network_orchestrator.py @@ -22,8 +22,9 @@ import sys import docker import time +import traceback from docker.types import Mount -from common import logger, util +from common import logger, util, mqtt from net_orc.listener import Listener from net_orc.network_event import NetworkEvent from net_orc.network_validator import NetworkValidator @@ -223,7 +224,9 @@ def _device_discovered(self, mac_addr): #self._ovs.add_arp_inspection_filter(ip_address=device.ip_addr, # mac_address=device.mac_addr) - self._start_device_monitor(device) + # Don't monitor devices when in network only mode + if 'net_only' not in self._session.get_runtime_params(): + self._start_device_monitor(device) def _get_conn_stats(self): """ Extract information about the physical connection @@ -547,10 +550,6 @@ def _start_network_service(self, net_module): cap_add=['NET_ADMIN'], name=net_module.container_name, hostname=net_module.container_name, - # Undetermined version of docker seems to have broken - # DNS configuration (/etc/resolv.conf) Re-add when/if - # this network is utilized and DNS issue is resolved - #network=PRIVATE_DOCKER_NET, network_mode='none', privileged=True, detach=True, @@ -786,6 +785,48 @@ def restore_net(self): def get_session(self): return self._session + def network_adapters_checker(self, mqtt_client: mqtt.MQTT, topic: str): + """Checks for changes in network adapters + and sends a message to the frontend + """ + try: + adapters = self._session.detect_network_adapters_change() + if adapters: + mqtt_client.send_message(topic, adapters) + except Exception: + LOGGER.error(traceback.format_exc()) + + def is_device_connected(self): + """Check if device connected""" + return self._ip_ctrl.check_interface_status( + self._session.get_device_interface() + ) + + def internet_conn_checker(self, mqtt_client: mqtt.MQTT, topic: str): + """Checks internet connection and sends a status to frontend""" + + # Only check if Testrun is running not in single-intf mode + if (self.get_session().get_status() in [ + 'Waiting for Device', + 'Monitoring', + 'In Progress' + ]): + # Default message + message = {'connection': False} + iface = self._session.get_internet_interface() + + # Check that an internet intf has been selected + if iface and iface in self._ip_ctrl.get_sys_interfaces(): + + # Ping google.com from gateway container + internet_connection = self._ip_ctrl.ping_via_gateway( + 'google.com') + + if internet_connection: + message['connection'] = True + + # Broadcast via MQTT client + mqtt_client.send_message(topic, message) class NetworkModule: """Define all the properties of a Network Module""" diff --git a/framework/python/src/test_orc/test_orchestrator.py b/framework/python/src/test_orc/test_orchestrator.py index d38f888a1..a38371d07 100644 --- a/framework/python/src/test_orc/test_orchestrator.py +++ b/framework/python/src/test_orc/test_orchestrator.py @@ -60,6 +60,8 @@ def __init__(self, session, net_orc): os.path.dirname( os.path.dirname( os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))) + self._test_modules_running = [] + self._current_module = 0 def start(self): LOGGER.debug("Starting test orchestrator") @@ -102,7 +104,13 @@ def run_test_modules(self): test_modules.append(module) self.get_session().add_total_tests(len(module.tests)) - for module in test_modules: + # Store enabled test modules in the TestsOrchectrator object + self._test_modules_running = test_modules + self._current_module = 0 + + for index, module in enumerate(test_modules): + + self._current_module = index self._run_test_module(module) LOGGER.info("All tests complete") @@ -362,7 +370,14 @@ def _run_test_module(self, module): LOGGER.info(f"Running test module {module.name}") # Get all tests to be executed and set to in progress - for test in module.tests: + for current_test,test in enumerate(module.tests): + + # Check that device is connected + if not self._net_orc.is_device_connected(): + LOGGER.error("Device was disconnected") + self._set_test_modules_error(current_test) + self._session.set_status("Cancelled") + return test_copy = copy.deepcopy(test) test_copy.result = "In Progress" @@ -486,19 +501,25 @@ def _run_test_module(self, module): try: with open(results_file, "r", encoding="utf-8-sig") as f: + + # Load results from JSON file module_results_json = json.load(f) module_results = module_results_json["results"] for test_result in module_results: - # Convert dict into TestCase object + # Convert dict from json into TestCase object test_case = TestCase( name=test_result["name"], description=test_result["description"], expected_behavior=test_result["expected_behavior"], required_result=test_result["required_result"], result=test_result["result"]) - test_case.result=test_result["result"] + # Any informational test should always report informational + if test_case.required_result == "Informational": + test_case.result = "Informational" + + # Add steps to resolve if test is non-compliant if (test_case.result == "Non-Compliant" and "recommendations" in test_result): test_case.recommendations = test_result["recommendations"] @@ -729,3 +750,13 @@ def get_test_case(self, name): def get_session(self): return self._session + + def _set_test_modules_error(self, current_test): + """Set all remaining tests to error""" + for i in range(self._current_module, len(self._test_modules_running)): + start_idx = current_test if i == self._current_module else 0 + for j in range(start_idx, len(self._test_modules_running[i].tests)): + self.get_session().set_test_result_error( + self._test_modules_running[i].tests[j] + ) + diff --git a/framework/requirements.txt b/framework/requirements.txt index c31978d99..0484905ee 100644 --- a/framework/requirements.txt +++ b/framework/requirements.txt @@ -1,8 +1,8 @@ # Requirements for the core module -requests<2.32.0 +requests==2.32.3 # Requirements for the net_orc module -docker==7.0.0 +docker==7.1.0 ipaddress==1.0.23 netifaces==0.11.0 scapy==2.5.0 @@ -21,6 +21,8 @@ pydantic==2.7.1 # Requirements for testing pytest==7.4.4 pytest-timeout==2.2.0 +responses==0.25.3 + # Requirements for the report markdown==3.5.2 @@ -31,3 +33,9 @@ pytz==2024.1 # Requirements for the risk profile python-dateutil==2.9.0 + +# Requirements for MQTT client +paho-mqtt==2.1.0 + +# Requirements for background tasks +APScheduler==3.10.4 diff --git a/make/DEBIAN/control b/make/DEBIAN/control index 488f69458..cecad9d17 100644 --- a/make/DEBIAN/control +++ b/make/DEBIAN/control @@ -1,5 +1,5 @@ Package: Testrun -Version: 1.3.1 +Version: 1.4-a Architecture: amd64 Maintainer: Google Homepage: https://github.com/google/testrun diff --git a/modules/test/base/README.md b/modules/test/base/README.md index e7f05d80e..24a725607 100644 --- a/modules/test/base/README.md +++ b/modules/test/base/README.md @@ -14,6 +14,13 @@ The ```config/module_config.json``` provides the name and description of the mod Within the ```python/src``` directory, basic logging and environment variables are provided to the test module. +Within the ```usr/local/etc``` directory there is a local copy of the MAC OUI database. This is just in case a new copy is unable to be downloaded during the install or update process. + +## GRPC server +Within the python directory, GRPC client code is provided to allow test modules to programmatically modify the various network services provided by Testrun. + +These currently include obtaining information about and controlling the DHCP servers in failover configuration. + ## Tests covered No tests are run by this module \ No newline at end of file diff --git a/modules/test/base/bin/setup b/modules/test/base/bin/setup new file mode 100644 index 000000000..23c96c513 --- /dev/null +++ b/modules/test/base/bin/setup @@ -0,0 +1,71 @@ +#!/bin/bash + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Define the local mount point to store local files to +export OUTPUT_DIR="/runtime/output" + +# Directory where all binaries will be loaded +export BIN_DIR="/testrun/bin" + +# Default interface should be veth0 for all containers +export IFACE=veth0 + +# Create a local user that matches the same as the host +# to be used for correct file ownership for various logs +# HOST_USER mapped in via docker container environemnt variables +useradd $HOST_USER + +# Set permissions on the output files +chown -R $HOST_USER $OUTPUT_DIR + +# Enable IPv6 for all containers +sysctl net.ipv6.conf.all.disable_ipv6=0 +sysctl -p + +# Read in the config file +CONF_FILE="/testrun/conf/module_config.json" +CONF=`cat $CONF_FILE` + +if [[ -z $CONF ]] +then + echo "No config file present at $CONF_FILE. Exiting startup." + exit 1 +fi + +# Extract the necessary config parameters +export MODULE_NAME=$(echo "$CONF" | jq -r '.config.meta.name') +export NETWORK_REQUIRED=$(echo "$CONF" | jq -r '.config.network') +export GRPC=$(echo "$CONF" | jq -r '.config.grpc') + +# Validate the module name is present +if [[ -z "$MODULE_NAME" || "$MODULE_NAME" == "null" ]] +then + echo "No module name present in $CONF_FILE. Exiting startup." + exit 1 +fi + +# Setup the PYTHONPATH so all imports work as expected +echo "Setting up PYTHONPATH..." +export PYTHONPATH=$($BIN_DIR/setup_python_path) +echo "PYTHONPATH: $PYTHONPATH" + +echo "Configuring binary files..." +$BIN_DIR/setup_binaries $BIN_DIR + +# Build all gRPC files from the proto for use in +# gRPC clients for communications to network modules +echo "Building gRPC files from available proto files..." +$BIN_DIR/setup_grpc_clients \ No newline at end of file diff --git a/modules/test/base/bin/start b/modules/test/base/bin/start index 37902b868..d1f29989f 100755 --- a/modules/test/base/bin/start +++ b/modules/test/base/bin/start @@ -14,4 +14,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -/testrun/bin/start_module \ No newline at end of file +# Allow one argument which is the unit test file to run +# instead of running the test module +UNIT_TEST_FILE=$1 + +source /testrun/bin/setup + +# Conditionally run start_module based on RUN +if [[ -z "$UNIT_TEST_FILE" ]];then + /testrun/bin/start_module +else + python3 $UNIT_TEST_FILE +fi diff --git a/modules/test/base/bin/start_module b/modules/test/base/bin/start_module index 0ee68fa6a..fb79cc018 100644 --- a/modules/test/base/bin/start_module +++ b/modules/test/base/bin/start_module @@ -1,102 +1,46 @@ -#!/bin/bash - -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Define the local mount point to store local files to -OUTPUT_DIR="/runtime/output" - -# Directory where all binaries will be loaded -BIN_DIR="/testrun/bin" - -# Default interface should be veth0 for all containers -IFACE=veth0 - -# Create a local user that matches the same as the host -# to be used for correct file ownership for various logs -# HOST_USER mapped in via docker container environemnt variables -useradd $HOST_USER - -# Set permissions on the output files -chown -R $HOST_USER $OUTPUT_DIR - -# Enable IPv6 for all containers -sysctl net.ipv6.conf.all.disable_ipv6=0 -sysctl -p - -# Read in the config file -CONF_FILE="/testrun/conf/module_config.json" -CONF=`cat $CONF_FILE` - -if [[ -z $CONF ]] -then - echo "No config file present at $CONF_FILE. Exiting startup." - exit 1 -fi - -# Extract the necessary config parameters -MODULE_NAME=$(echo "$CONF" | jq -r '.config.meta.name') -NETWORK_REQUIRED=$(echo "$CONF" | jq -r '.config.network') -GRPC=$(echo "$CONF" | jq -r '.config.grpc') - -# Validate the module name is present -if [[ -z "$MODULE_NAME" || "$MODULE_NAME" == "null" ]] -then - echo "No module name present in $CONF_FILE. Exiting startup." - exit 1 -fi - -# Setup the PYTHONPATH so all imports work as expected -echo "Setting up PYTHONPATH..." -export PYTHONPATH=$($BIN_DIR/setup_python_path) -echo "PYTHONPATH: $PYTHONPATH" - -# Build all gRPC files from the proto for use in -# gRPC clients for communications to network modules -echo "Building gRPC files from available proto files..." -$BIN_DIR/setup_grpc_clients - -echo "Configuring binary files..." -$BIN_DIR/setup_binaries $BIN_DIR - -echo "Starting module $MODULE_NAME..." - -# Only start network services if the test container needs -# a network connection to run its tests -if [ $NETWORK_REQUIRED == "true" ];then - # Wait for interface to become ready - $BIN_DIR/wait_for_interface $IFACE - - # Start network capture - $BIN_DIR/capture $MODULE_NAME $IFACE -fi - -# Start the grpc server -if [[ ! -z $GRPC && ! $GRPC == "null" ]] -then - GRPC_PORT=$(echo "$GRPC" | jq -r '.port') - if [[ ! -z $GRPC_PORT && ! $GRPC_PORT == "null" ]] - then - echo "gRPC port resolved from config: $GRPC_PORT" - $BIN_DIR/start_grpc "-p $GRPC_PORT" - else - $BIN_DIR/start_grpc - fi -fi - -# Small pause to let all core services stabalize -sleep 3 - -# Start the test module +#!/bin/bash + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +echo "Starting module $MODULE_NAME..." + +# Only start network services if the test container needs +# a network connection to run its tests +if [ $NETWORK_REQUIRED == "true" ];then + # Wait for interface to become ready + $BIN_DIR/wait_for_interface $IFACE + + # Start network capture + $BIN_DIR/capture $MODULE_NAME $IFACE +fi + +# Start the grpc server +if [[ ! -z $GRPC && ! $GRPC == "null" ]] +then + GRPC_PORT=$(echo "$GRPC" | jq -r '.port') + if [[ ! -z $GRPC_PORT && ! $GRPC_PORT == "null" ]] + then + echo "gRPC port resolved from config: $GRPC_PORT" + $BIN_DIR/start_grpc "-p $GRPC_PORT" + else + $BIN_DIR/start_grpc + fi +fi + +# Small pause to let all core services stabalize +sleep 3 + +# Start the test module $BIN_DIR/start_test_module $MODULE_NAME $IFACE \ No newline at end of file diff --git a/modules/test/base/python/src/test_module.py b/modules/test/base/python/src/test_module.py index 00f74df82..deed0d978 100644 --- a/modules/test/base/python/src/test_module.py +++ b/modules/test/base/python/src/test_module.py @@ -17,6 +17,7 @@ import os import util from datetime import datetime +import traceback LOGGER = None RESULTS_DIR = '/runtime/output/' @@ -48,9 +49,9 @@ def __init__(self, def _add_logger(self, log_name, module_name, log_dir=None): global LOGGER - LOGGER = logger.get_logger(name=log_name, + LOGGER = logger.get_logger(name=log_name, # pylint: disable=E1123 log_file=module_name, - log_dir=log_dir) # pylint: disable=E1123 + log_dir=log_dir) def generate_module_report(self): pass @@ -113,11 +114,17 @@ def run_tests(self): except Exception as e: # pylint: disable=W0718 LOGGER.error(f'An error occurred whilst running {test["name"]}') LOGGER.error(e) + traceback.print_exc() else: LOGGER.info(f'Test {test["name"]} not implemented. Skipping') + test['result'] = 'Error' + test['description'] = 'This test could not be found' else: LOGGER.debug(f'Test {test["name"]} is disabled') + # To be added in v1.3.2 + # result = 'Disabled', 'This test is disabled and did not run' + if result is not None: # Compliant or non-compliant as a boolean only if isinstance(result, bool): @@ -182,7 +189,7 @@ def _write_results(self, results): def _get_device_ipv4(self): command = f"""/testrun/bin/get_ipv4_addr {self._ipv4_subnet} {self._device_mac.upper()}""" - text = util.run_command(command)[0] + text = util.run_command(command)[0] # pylint: disable=E1120 if text: return text.split('\n')[0] return None diff --git a/modules/test/conn/python/src/connection_module.py b/modules/test/conn/python/src/connection_module.py index 5e8b78ec3..88dd40393 100644 --- a/modules/test/conn/python/src/connection_module.py +++ b/modules/test/conn/python/src/connection_module.py @@ -15,7 +15,7 @@ import util import time import traceback -from scapy.all import rdpcap, DHCP, ARP, Ether, IPv6, ICMPv6ND_NS +from scapy.all import rdpcap, DHCP, ARP, Ether, ICMP, IPv6, ICMPv6ND_NS from test_module import TestModule from dhcp1.client import Client as DHCPClient1 from dhcp2.client import Client as DHCPClient2 @@ -39,7 +39,14 @@ class ConnectionModule(TestModule): """Connection Test module""" - def __init__(self, module, log_dir=None, conf_file=None, results_dir=None): + def __init__(self, + module, + log_dir=None, + conf_file=None, + results_dir=None, + startup_capture_file=STARTUP_CAPTURE_FILE, + monitor_capture_file=MONITOR_CAPTURE_FILE): + super().__init__(module_name=module, log_name=LOG_NAME, log_dir=log_dir, @@ -47,6 +54,8 @@ def __init__(self, module, log_dir=None, conf_file=None, results_dir=None): results_dir=results_dir) global LOGGER LOGGER = self._get_logger() + self.startup_capture_file = startup_capture_file + self.monitor_capture_file = monitor_capture_file self._port_stats = PortStatsUtil(logger=LOGGER) self.dhcp1_client = DHCPClient1() self.dhcp2_client = DHCPClient2() @@ -106,7 +115,8 @@ def _connection_switch_arp_inspection(self): no_arp = True # Read all the pcap files - packets = rdpcap(STARTUP_CAPTURE_FILE) + rdpcap(MONITOR_CAPTURE_FILE) + packets = rdpcap(self.startup_capture_file) + rdpcap( + self.monitor_capture_file) for packet in packets: # We are not interested in packets unless they are ARP packets @@ -123,12 +133,8 @@ def _connection_switch_arp_inspection(self): # Check MAC address matches IP address if (arp_packet.hwsrc == self._device_mac - and (arp_packet.psrc not in ( - self._device_ipv4_addr, - '0.0.0.0' - )) and not arp_packet.psrc.startswith( - '169.254' - )): + and (arp_packet.psrc not in (self._device_ipv4_addr, '0.0.0.0')) + and not arp_packet.psrc.startswith('169.254')): LOGGER.info(f'Bad ARP packet detected for MAC: {self._device_mac}') LOGGER.info(f'''ARP packet from IP {arp_packet.psrc} does not match {self._device_ipv4_addr}''') @@ -145,7 +151,8 @@ def _connection_switch_dhcp_snooping(self): disallowed_dhcp_types = [2, 4, 5, 6, 9, 10, 12, 13, 15, 17] # Read all the pcap files - packets = rdpcap(STARTUP_CAPTURE_FILE) + rdpcap(MONITOR_CAPTURE_FILE) + packets = rdpcap(self.startup_capture_file) + rdpcap( + self.monitor_capture_file) for packet in packets: # We are not interested in packets unless they are DHCP packets @@ -158,6 +165,11 @@ def _connection_switch_dhcp_snooping(self): dhcp_type = self._get_dhcp_type(packet) if dhcp_type in disallowed_dhcp_types: + + # Check if packet is responding with port unreachable + if ICMP in packet and packet[ICMP].type == 3: + continue + return False, 'Device has sent disallowed DHCP message' return True, 'Device does not act as a DHCP server' @@ -220,7 +232,8 @@ def _connection_single_ip(self): return result, 'No MAC address found.' # Read all the pcap files containing DHCP packet information - packets = rdpcap(STARTUP_CAPTURE_FILE) + rdpcap(MONITOR_CAPTURE_FILE) + packets = rdpcap(self.startup_capture_file) + rdpcap( + self.monitor_capture_file) # Extract MAC addresses from DHCP packets mac_addresses = set() @@ -394,8 +407,9 @@ def _connection_ipv6_slaac(self): return result def _has_slaac_addres(self): - packet_capture = (rdpcap(STARTUP_CAPTURE_FILE) + - rdpcap(MONITOR_CAPTURE_FILE) + rdpcap(DHCP_CAPTURE_FILE)) + packet_capture = (rdpcap(self.startup_capture_file) + + rdpcap(self.monitor_capture_file) + + rdpcap(DHCP_CAPTURE_FILE)) sends_ipv6 = False for packet_number, packet in enumerate(packet_capture, start=1): if IPv6 in packet and packet.src == self._device_mac: @@ -432,7 +446,7 @@ def _ping(self, host, ipv6=False): cmd += ' -6 ' if ipv6 else '' cmd += str(host) #cmd = 'ping -c 1 ' + str(host) - success = util.run_command(cmd, output=False) + success = util.run_command(cmd, output=False) # pylint: disable=E1120 return success def restore_failover_dhcp_server(self, subnet): diff --git a/modules/test/conn/python/src/dhcp_util.py b/modules/test/conn/python/src/dhcp_util.py index be5f0cac2..3654d0401 100644 --- a/modules/test/conn/python/src/dhcp_util.py +++ b/modules/test/conn/python/src/dhcp_util.py @@ -207,7 +207,7 @@ def is_lease_active(self, lease): def ping(self, host): cmd = 'ping -c 1 ' + str(host) - success = util.run_command(cmd, output=False) + success = util.run_command(cmd, output=False) # pylint: disable=E1120 return success def add_reserved_lease(self, diff --git a/modules/test/dns/README.md b/modules/test/dns/README.md index 13f0df5fd..79bce57f7 100644 --- a/modules/test/dns/README.md +++ b/modules/test/dns/README.md @@ -15,4 +15,5 @@ Within the ```python/src``` directory, the below tests are executed. | ID | Description | Expected behavior | Required result |---|---|---|---| | dns.network.hostname_resolution | Verifies that the device resolves hostnames | The device sends DNS requests | Required | -| dns.network.from_dhcp | Verifies that the device allows for a DNS server to be provided by the DHCP server | The device sends DNS requests to the DNS server provided by the DHCP server | Roadmap | \ No newline at end of file +| dns.network.from_dhcp | Verifies that the device allows for a DNS server to be provided by the DHCP server | The device sends DNS requests to the DNS server provided by the DHCP server | Roadmap | +| dns.mdns | Does the device has MDNS (or any kind of IP multicast) | Device may send MDNS requests | Informational | \ No newline at end of file diff --git a/modules/test/dns/conf/module_config.json b/modules/test/dns/conf/module_config.json index 13c9b3236..f048d5deb 100644 --- a/modules/test/dns/conf/module_config.json +++ b/modules/test/dns/conf/module_config.json @@ -31,6 +31,12 @@ "recommendations": [ "Install a DNS client that supports fetching DNS servers from DHCP options" ] + }, + { + "name": "dns.mdns", + "test_description": "Does the device has MDNS (or any kind of IP multicast)", + "expected_behavior": "Device may send MDNS requests", + "required_result": "Informational" } ] } diff --git a/modules/test/dns/python/src/dns_module.py b/modules/test/dns/python/src/dns_module.py index 607a026b5..c04e289d3 100644 --- a/modules/test/dns/python/src/dns_module.py +++ b/modules/test/dns/python/src/dns_module.py @@ -13,12 +13,13 @@ # limitations under the License. """DNS test module""" import subprocess -from scapy.all import rdpcap, DNS, IP +from scapy.all import rdpcap, DNS, IP, Ether from test_module import TestModule import os +from collections import Counter LOG_NAME = 'test_dns' -MODULE_REPORT_FILE_NAME='dns_report.html' +MODULE_REPORT_FILE_NAME = 'dns_report.html' DNS_SERVER_CAPTURE_FILE = '/runtime/network/dns.pcap' STARTUP_CAPTURE_FILE = '/runtime/device/startup.pcap' MONITOR_CAPTURE_FILE = '/runtime/device/monitor.pcap' @@ -41,9 +42,9 @@ def __init__(self, log_dir=log_dir, conf_file=conf_file, results_dir=results_dir) - self.dns_server_capture_file=dns_server_capture_file - self.startup_capture_file=startup_capture_file - self.monitor_capture_file=monitor_capture_file + self.dns_server_capture_file = dns_server_capture_file + self.startup_capture_file = startup_capture_file + self.monitor_capture_file = monitor_capture_file self._dns_server = '10.10.10.4' global LOGGER LOGGER = self._get_logger() @@ -55,18 +56,17 @@ def generate_module_report(self): html_content = '

DNS Module

' # Set the summary variables - local_requests = sum(1 for row in dns_table_data - if row['Destination'] == - self._dns_server and row['Type'] == 'Query') - external_requests = sum(1 for row in dns_table_data - if row['Destination'] != - self._dns_server and row['Type'] == 'Query') + local_requests = sum( + 1 for row in dns_table_data + if row['Destination'] == self._dns_server and row['Type'] == 'Query') + external_requests = sum( + 1 for row in dns_table_data + if row['Destination'] != self._dns_server and row['Type'] == 'Query') - total_requests = sum(1 for row in dns_table_data - if row['Type'] == 'Query') + total_requests = sum(1 for row in dns_table_data if row['Type'] == 'Query') total_responses = sum(1 for row in dns_table_data - if row['Type'] == 'Response') + if row['Type'] == 'Response') # Add summary table html_content += (f''' @@ -99,18 +99,26 @@ def generate_module_report(self): Destination Type URL + Count ''' - for row in dns_table_data: - table_content += (f''' - - {row['Source']} - {row['Destination']} - {row['Type']} - {row['Data']} - ''') + # Count unique combinations + counter = Counter( + (row['Source'], row['Destination'], row['Type'], row['Data']) + for row in dns_table_data) + + # Generate the HTML table with the count column + for (src, dst, typ, dat), count in counter.items(): + table_content += f''' + + {src} + {dst} + {typ} + {dat} + {count} + ''' table_content += ''' @@ -149,26 +157,28 @@ def extract_dns_data(self): # Iterate through DNS packets for packet in packets: if DNS in packet and packet.haslayer(IP): - source_ip = packet[IP].src - destination_ip = packet[IP].dst - dns_layer = packet[DNS] - - # 'qr' field indicates query (0) or response (1) - dns_type = 'Query' if dns_layer.qr == 0 else 'Response' - - # Check for the presence of DNS query name - if hasattr(dns_layer, 'qd') and dns_layer.qd is not None: + + # Check if either source or destination MAC matches the device + if self._device_mac in (packet[Ether].src, packet[Ether].dst): + source_ip = packet[IP].src + destination_ip = packet[IP].dst + dns_layer = packet[DNS] + # 'qr' field indicates query (0) or response (1) + dns_type = 'Query' if dns_layer.qr == 0 else 'Response' + + # Check for the presence of DNS query name + if hasattr(dns_layer, 'qd') and dns_layer.qd is not None: qname = dns_layer.qd.qname.decode() if dns_layer.qd.qname else 'N/A' - else: + else: qname = 'N/A' - dns_data.append({ - 'Timestamp': float(packet.time), # Timestamp of the DNS packet - 'Source': source_ip, - 'Destination': destination_ip, - 'Type': dns_type, - 'Data': qname[:-1] - }) + dns_data.append({ + 'Timestamp': float(packet.time), # Timestamp of the DNS packet + 'Source': source_ip, + 'Destination': destination_ip, + 'Type': dns_type, + 'Data': qname[:-1] + }) # Filter unique entries based on 'Timestamp' # DNS Server will duplicate messages caught by @@ -273,10 +283,10 @@ def _exec_tcpdump(self, tcpdump_filter, capture_file): LOGGER.debug('tcpdump command: ' + command) with subprocess.Popen(command, - universal_newlines=True, - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) as process: + universal_newlines=True, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) as process: text = str(process.stdout.read()).rstrip() LOGGER.debug('tcpdump response: ' + text) diff --git a/modules/test/ntp/python/src/ntp_module.py b/modules/test/ntp/python/src/ntp_module.py index 453c992e6..be27abbad 100644 --- a/modules/test/ntp/python/src/ntp_module.py +++ b/modules/test/ntp/python/src/ntp_module.py @@ -14,8 +14,8 @@ """NTP test module""" from test_module import TestModule from scapy.all import rdpcap, IP, IPv6, NTP, UDP, Ether -from datetime import datetime import os +from collections import defaultdict LOG_NAME = 'test_ntp' MODULE_REPORT_FILE_NAME = 'ntp_report.html' @@ -69,6 +69,33 @@ def generate_module_report(self): total_responses = sum(1 for row in ntp_table_data if row['Type'] == 'Server') + # Initialize a dictionary to store timestamps for each unique combination + timestamps = defaultdict(list) + + # Collect timestamps for each unique combination + for row in ntp_table_data: + # Add the timestamp to the corresponding combination + key = (row['Source'], row['Destination'], row['Type'], row['Version']) + timestamps[key].append(row['Timestamp']) + + # Calculate the average time between requests for each unique combination + average_time_between_requests = {} + + for key, times in timestamps.items(): + # Sort the timestamps + times.sort() + + # Calculate the time differences between consecutive timestamps + time_diffs = [t2 - t1 for t1, t2 in zip(times[:-1], times[1:])] + + # Calculate the average of the time differences + if time_diffs: + avg_diff = sum(time_diffs) / len(time_diffs) + else: + avg_diff = 0 # one timestamp, the average difference is 0 + + average_time_between_requests[key] = avg_diff + # Add summary table html_content += (f''' @@ -92,7 +119,6 @@ def generate_module_report(self): ''') if total_requests + total_responses > 0: - table_content = '''
@@ -101,37 +127,39 @@ def generate_module_report(self): - + + ''' - for row in ntp_table_data: - - # Timestamp of the NTP packet - dt_object = datetime.utcfromtimestamp(row['Timestamp']) - - # Extract milliseconds from the fractional part of the timestamp - milliseconds = int((row['Timestamp'] % 1) * 1000) + # Generate the HTML table with the count column + for (src, dst, typ, + version), avg_diff in average_time_between_requests.items(): + cnt = len(timestamps[(src, dst, typ, version)]) - # Format the datetime object with milliseconds - formatted_time = dt_object.strftime( - '%b %d, %Y %H:%M:%S.') + f'{milliseconds:03d}' + # Sync Average only applies to client requests + if 'Client' in typ: + # Convert avg_diff to seconds and format it + avg_diff_seconds = avg_diff + avg_formatted_time = f'{avg_diff_seconds:.3f} seconds' + else: + avg_formatted_time = 'N/A' - table_content += (f''' + table_content += f''' - - - - - - ''') + + + + + + + ''' table_content += '''
Destination Type VersionTimestampCountSync Request Average
{row['Source']}{row['Destination']}{row['Type']}{row['Version']}{formatted_time}
{src}{dst}{typ}{version}{cnt}{avg_formatted_time}
''' - html_content += table_content else: @@ -159,8 +187,8 @@ def extract_ntp_data(self): # Read the pcap files packets = (rdpcap(self.startup_capture_file) + - rdpcap(self.monitor_capture_file) + - rdpcap(self.ntp_server_capture_file)) + rdpcap(self.monitor_capture_file) + + rdpcap(self.ntp_server_capture_file)) # Iterate through NTP packets for packet in packets: @@ -171,6 +199,10 @@ def extract_ntp_data(self): # Local NTP server syncs to external servers so we need to filter only # for traffic to/from the device if self._device_mac in (source_mac, destination_mac): + + source_ip = None + dest_ip = None + if IP in packet: source_ip = packet[IP].src dest_ip = packet[IP].dst @@ -218,6 +250,9 @@ def _ntp_network_ntp_support(self): for packet in packet_capture: if NTP in packet and packet.src == self._device_mac: + + dest_ip = None + if IP in packet: dest_ip = packet[IP].dst elif IPv6 in packet: @@ -229,16 +264,17 @@ def _ntp_network_ntp_support(self): device_sends_ntp3 = True LOGGER.info(f'Device sent NTPv3 request to {dest_ip}') - if not (device_sends_ntp3 or device_sends_ntp4): - result = False, 'Device has not sent any NTP requests' - elif device_sends_ntp3 and device_sends_ntp4: + result = False, 'Device has not sent any NTP requests' + + if device_sends_ntp3 and device_sends_ntp4: result = False, ('Device sent NTPv3 and NTPv4 packets. ' + - 'NTPv3 is not allowed.') + 'NTPv3 is not allowed') elif device_sends_ntp3: result = False, ('Device sent NTPv3 packets. ' - 'NTPv3 is not allowed.') + 'NTPv3 is not allowed') elif device_sends_ntp4: - result = True, 'Device sent NTPv4 packets.' + result = True, 'Device sent NTPv4 packets' + LOGGER.info(result[1]) return result @@ -255,6 +291,7 @@ def _ntp_network_ntp_dhcp(self): for packet in packet_capture: if NTP in packet and packet.src == self._device_mac: device_sends_ntp = True + dest_ip = None if IP in packet: dest_ip = packet[IP].dst elif IPv6 in packet: @@ -266,17 +303,17 @@ def _ntp_network_ntp_dhcp(self): LOGGER.info('Device sent NTP request to non-DHCP provided NTP server') ntp_to_remote = True + result = 'Feature Not Detected', 'Device has not sent any NTP requests' + if device_sends_ntp: if ntp_to_local and ntp_to_remote: result = False, ('Device sent NTP request to DHCP provided ' + 'server and non-DHCP provided server') elif ntp_to_remote: result = ('Feature Not Detected', - 'Device sent NTP request to non-DHCP provided server') + 'Device sent NTP request to non-DHCP provided server') elif ntp_to_local: result = True, 'Device sent NTP request to DHCP provided server' - else: - result = 'Feature Not Detected', 'Device has not sent any NTP requests' LOGGER.info(result[1]) return result diff --git a/modules/test/protocol/bin/start_test_module b/modules/test/protocol/bin/start_test_module index a0754836c..d85ae7d6b 100644 --- a/modules/test/protocol/bin/start_test_module +++ b/modules/test/protocol/bin/start_test_module @@ -1,53 +1,53 @@ -#!/bin/bash - -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Setup and start the connection test module - -# Define where the python source files are located -PYTHON_SRC_DIR=/testrun/python/src - -# Fetch module name -MODULE_NAME=$1 - -# Default interface should be veth0 for all containers -DEFAULT_IFACE=veth0 - -# Allow a user to define an interface by passing it into this script -DEFINED_IFACE=$2 - -# Select which interace to use -if [[ -z $DEFINED_IFACE || "$DEFINED_IFACE" == "null" ]] -then - echo "No interface defined, defaulting to veth0" - INTF=$DEFAULT_IFACE -else - INTF=$DEFINED_IFACE -fi - -# Create and set permissions on the log files -LOG_FILE=/runtime/output/$MODULE_NAME.log -RESULT_FILE=/runtime/output/$MODULE_NAME-result.json -touch $LOG_FILE -touch $RESULT_FILE -chown $HOST_USER $LOG_FILE -chown $HOST_USER $RESULT_FILE - -# Run the python script that will execute the tests for this module -# -u flag allows python print statements -# to be logged by docker by running unbuffered -python3 -u $PYTHON_SRC_DIR/run.py "-m $MODULE_NAME" - +#!/bin/bash + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Setup and start the connection test module + +# Define where the python source files are located +PYTHON_SRC_DIR=/testrun/python/src + +# Fetch module name +MODULE_NAME=$1 + +# Default interface should be veth0 for all containers +DEFAULT_IFACE=veth0 + +# Allow a user to define an interface by passing it into this script +DEFINED_IFACE=$2 + +# Select which interace to use +if [[ -z $DEFINED_IFACE || "$DEFINED_IFACE" == "null" ]] +then + echo "No interface defined, defaulting to veth0" + INTF=$DEFAULT_IFACE +else + INTF=$DEFINED_IFACE +fi + +# Create and set permissions on the log files +LOG_FILE=/runtime/output/$MODULE_NAME.log +RESULT_FILE=/runtime/output/$MODULE_NAME-result.json +touch $LOG_FILE +touch $RESULT_FILE +chown $HOST_USER $LOG_FILE +chown $HOST_USER $RESULT_FILE + +# Run the python script that will execute the tests for this module +# -u flag allows python print statements +# to be logged by docker by running unbuffered +python3 -u $PYTHON_SRC_DIR/run.py "-m $MODULE_NAME" + echo Module has finished \ No newline at end of file diff --git a/modules/test/protocol/python/requirements.txt b/modules/test/protocol/python/requirements.txt index 57917735d..5b54a724d 100644 --- a/modules/test/protocol/python/requirements.txt +++ b/modules/test/protocol/python/requirements.txt @@ -1,7 +1,7 @@ # Required for BACnet protocol tests -netifaces -BAC0 -pytz +netifaces==0.11.0 +BAC0==23.7.3 +pytz==2024.1 # Required for Modbus protocol tests -pymodbus \ No newline at end of file +pymodbus==3.7.0 \ No newline at end of file diff --git a/modules/test/protocol/python/src/protocol_modbus.py b/modules/test/protocol/python/src/protocol_modbus.py index 925e9517a..a722f928e 100644 --- a/modules/test/protocol/python/src/protocol_modbus.py +++ b/modules/test/protocol/python/src/protocol_modbus.py @@ -103,7 +103,7 @@ def __init__(self, log, device_ip, config): self._discrete_input_enabled = False # Initialize the modbus client - self.client = ModbusClient(device_ip, self._port) + self.client = ModbusClient(host=device_ip, port=self._port) # Connections created from this method are simple socket connections # and aren't indicative of valid modbus diff --git a/modules/test/services/python/src/services_module.py b/modules/test/services/python/src/services_module.py index bfa232c87..b14c74234 100644 --- a/modules/test/services/python/src/services_module.py +++ b/modules/test/services/python/src/services_module.py @@ -200,7 +200,7 @@ def _process_port_results(self): def _scan_tcp_ports(self): max_port = 1000 LOGGER.info('Running nmap TCP port scan') - nmap_results = util.run_command( + nmap_results = util.run_command( # pylint: disable=E1120 f'''nmap --open -sT -sV -Pn -v -p 1-{max_port} --version-intensity 7 -T4 -oX - {self._ipv4_addr}''')[0] @@ -228,7 +228,7 @@ def _scan_udp_ports(self): port_list = ','.join(ports) LOGGER.info('Running nmap UDP port scan') LOGGER.debug('UDP ports: ' + str(port_list)) - nmap_results = util.run_command( + nmap_results = util.run_command( # pylint: disable=E1120 f'nmap -sU -sV -p {port_list} -oX - {self._ipv4_addr}')[0] LOGGER.info('UDP port scan complete') nmap_results_json = self._nmap_results_to_json(nmap_results) diff --git a/modules/test/tls/bin/get_tls_client_connections.sh b/modules/test/tls/bin/get_tls_client_connections.sh index e2e6da91b..7335cac80 100755 --- a/modules/test/tls/bin/get_tls_client_connections.sh +++ b/modules/test/tls/bin/get_tls_client_connections.sh @@ -1,32 +1,32 @@ -#!/bin/bash - -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -CAPTURE_FILE="$1" -SRC_IP="$2" -PROTOCOL=$3 - -TSHARK_OUTPUT="-T json -e ip.src -e tcp.dstport -e ip.dst" -TSHARK_FILTER="ip.src == $SRC_IP and tls" - -# Add a protocol filter if defined -if [ -n "$PROTOCOL" ];then - TSHARK_FILTER="$TSHARK_FILTER and $PROTOCOL" -fi - -response=$(tshark -r "$CAPTURE_FILE" $TSHARK_OUTPUT $TSHARK_FILTER) - -echo "$response" +#!/bin/bash + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +CAPTURE_FILE="$1" +SRC_IP="$2" +PROTOCOL=$3 + +TSHARK_OUTPUT="-T json -e ip.src -e tcp.dstport -e ip.dst" +TSHARK_FILTER="ip.src == $SRC_IP and tls" + +# Add a protocol filter if defined +if [ -n "$PROTOCOL" ];then + TSHARK_FILTER="$TSHARK_FILTER and $PROTOCOL" +fi + +response=$(tshark -r "$CAPTURE_FILE" $TSHARK_OUTPUT $TSHARK_FILTER) + +echo "$response" \ No newline at end of file diff --git a/modules/test/tls/conf/module_config.json b/modules/test/tls/conf/module_config.json index cd77f8299..c74bfd667 100644 --- a/modules/test/tls/conf/module_config.json +++ b/modules/test/tls/conf/module_config.json @@ -32,6 +32,27 @@ "Disable connections to unsecure services", "Ensure any URLs connected to are secure (https)" ] + }, + { + "name": "security.tls.v1_3_server", + "test_description": "Check the device web server TLS 1.3 & certificate is valid", + "expected_behavior": "TLS 1.3 certificate is issued to the web browser client when accessed", + "required_result": "Informational", + "recommendations": [ + "Enable TLS 1.3 support in the web server configuration", + "Disable TLS 1.0 and 1.1", + "Sign the certificate used by the web server" + ] + }, + { + "name": "security.tls.v1_3_client", + "test_description": "Device uses TLS with connection to an external service on port 443 (or any other port which could be running the webserver-HTTPS)", + "expected_behavior": "The packet indicates a TLS connection with at least TLS 1.3", + "required_result": "Informational", + "recommendations": [ + "Disable connections to unsecure services", + "Ensure any URLs connected to are secure (https)" + ] } ] } diff --git a/modules/test/tls/python/requirements-test.txt b/modules/test/tls/python/requirements-test.txt new file mode 100644 index 000000000..93b351f44 --- /dev/null +++ b/modules/test/tls/python/requirements-test.txt @@ -0,0 +1 @@ +scapy \ No newline at end of file diff --git a/modules/test/tls/python/requirements.txt b/modules/test/tls/python/requirements.txt index 7624a2c68..846a224f3 100644 --- a/modules/test/tls/python/requirements.txt +++ b/modules/test/tls/python/requirements.txt @@ -1,5 +1,5 @@ -cryptography==42.0.4 # Do not upgrade until TLS module can be fixed to account for removed x509 property in version 39 -pyOpenSSL==24.1.0 +cryptography==38.0.0 # Do not upgrade until TLS module can be fixed to account for removed x509 property in version 39 +pyOpenSSL==23.0.0 lxml==5.1.0 # Requirement of pyshark but if upgraded automatically above 5.1 will cause a python crash pyshark==0.6 -requests==2.32.0 +requests==2.32.3 diff --git a/modules/test/tls/python/src/tls_util.py b/modules/test/tls/python/src/tls_util.py index d8c1d7a16..0364479c6 100644 --- a/modules/test/tls/python/src/tls_util.py +++ b/modules/test/tls/python/src/tls_util.py @@ -372,6 +372,8 @@ def validate_tls_server(self, host, tls_version): public_key = self.get_public_key(public_cert) if public_key: key_valid = self.verify_public_key(public_key) + else: + key_valid = [0] sig_valid = self.validate_signature(host) @@ -527,7 +529,7 @@ def process_hello_packets(self, LOGGER.info('Checking client ciphers: ' + str(packet)) if packet['cipher_support']['ecdh'] and packet['cipher_support'][ 'ecdsa']: - LOGGER.info('Valid ciphers detected') + LOGGER.info('Required ciphers detected') client_hello_results['valid'].append(packet) # If a previous hello packet to the same destination failed, # we can now remove it as it has passed on a different attempt @@ -537,7 +539,7 @@ def process_hello_packets(self, if packet['dst_ip'] in str(invalid_packet): client_hello_results['invalid'].remove(invalid_packet) else: - LOGGER.info('Invalid ciphers detected') + LOGGER.info('Required ciphers not detected') if packet['dst_ip'] not in allowed_protocol_client_ips: if packet['dst_ip'] not in str(client_hello_results['invalid']): client_hello_results['invalid'].append(packet) diff --git a/modules/test/tls/tls.Dockerfile b/modules/test/tls/tls.Dockerfile index cedf9531b..987ede591 100644 --- a/modules/test/tls/tls.Dockerfile +++ b/modules/test/tls/tls.Dockerfile @@ -31,14 +31,20 @@ COPY $MODULE_DIR/conf /testrun/conf # Copy over all binary files COPY $MODULE_DIR/bin /testrun/bin +# Remove incorrect line endings +RUN dos2unix /testrun/bin/* + +# Make sure all the bin files are executable +RUN chmod u+x /testrun/bin/* + # Copy over all python files COPY $MODULE_DIR/python /testrun/python -#Install all python requirements for the module +# Install all python requirements for the module RUN pip3 install -r /testrun/python/requirements.txt +# Install all python requirements for the modules unit test +RUN pip3 install -r /testrun/python/requirements-test.txt + # Create a directory inside the container to store the root certificates RUN mkdir -p /testrun/root_certs - - - diff --git a/modules/ui/angular.json b/modules/ui/angular.json index d72fee51f..0bf42377f 100644 --- a/modules/ui/angular.json +++ b/modules/ui/angular.json @@ -25,14 +25,15 @@ "inlineStyleLanguage": "scss", "assets": ["src/favicon.ico", "src/assets"], "styles": ["src/styles.scss"], - "scripts": [] + "scripts": [], + "allowedCommonJsDependencies": ["mqtt-browser"] }, "configurations": { "production": { "budgets": [ { "type": "initial", - "maximumWarning": "1000kb", + "maximumWarning": "1500kb", "maximumError": "3000kb" }, { @@ -93,6 +94,7 @@ } }, "cli": { - "schematicCollections": ["@angular-eslint/schematics"] + "schematicCollections": ["@angular-eslint/schematics"], + "analytics": false } } diff --git a/testing/unit/build.sh b/modules/ui/build.Dockerfile similarity index 77% rename from testing/unit/build.sh rename to modules/ui/build.Dockerfile index db84e0299..180ad9747 100644 --- a/testing/unit/build.sh +++ b/modules/ui/build.Dockerfile @@ -1,5 +1,3 @@ -#!/bin/bash -e - # Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,4 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -sudo docker build -f testing/unit/unit_test.Dockerfile -t testrun/unit-test . \ No newline at end of file +# Image name: testrun/build-ui +FROM node@sha256:ffebb4405810c92d267a764b21975fb2d96772e41877248a37bf3abaa0d3b590 as build + +# Set the working directory +WORKDIR /modules/ui + diff --git a/modules/ui/package-lock.json b/modules/ui/package-lock.json index e6903631a..637dc47e4 100644 --- a/modules/ui/package-lock.json +++ b/modules/ui/package-lock.json @@ -22,6 +22,7 @@ "@ngrx/effects": "^17.1.1", "@ngrx/store": "^17.0.1", "ngx-mask": "^16.4.2", + "ngx-mqtt": "^17.0.0", "rxjs": "~7.8.0", "tslib": "^2.6.2", "zone.js": "^0.14.4" @@ -6531,14 +6532,12 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -6594,7 +6593,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -6713,7 +6711,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "funding": [ { "type": "github", @@ -6736,8 +6733,7 @@ "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, "node_modules/bytes": { "version": "3.1.2", @@ -7089,6 +7085,15 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "node_modules/commist": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/commist/-/commist-1.1.0.tgz", + "integrity": "sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==", + "dependencies": { + "leven": "^2.1.0", + "minimist": "^1.1.0" + } + }, "node_modules/common-path-prefix": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", @@ -7167,8 +7172,21 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } }, "node_modules/connect": { "version": "3.7.0", @@ -7599,7 +7617,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -7862,6 +7879,17 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -7946,7 +7974,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "dependencies": { "once": "^1.4.0" } @@ -9156,8 +9183,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.3", @@ -9244,7 +9270,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -9282,7 +9307,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -9292,7 +9316,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -9437,6 +9460,15 @@ "node": ">= 0.4" } }, + "node_modules/help-me": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-3.0.0.tgz", + "integrity": "sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ==", + "dependencies": { + "glob": "^7.1.6", + "readable-stream": "^3.6.0" + } + }, "node_modules/hosted-git-info": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", @@ -9685,7 +9717,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -9788,7 +9819,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -9797,8 +9827,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "4.1.1", @@ -10462,6 +10491,15 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -10947,6 +10985,14 @@ "node": ">=0.10.0" } }, + "node_modules/leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -11445,7 +11491,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -11649,6 +11694,92 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mqtt": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-4.3.7.tgz", + "integrity": "sha512-ew3qwG/TJRorTz47eW46vZ5oBw5MEYbQZVaEji44j5lAUSQSqIEoul7Kua/BatBW0H0kKQcC9kwUHa1qzaWHSw==", + "dependencies": { + "commist": "^1.0.0", + "concat-stream": "^2.0.0", + "debug": "^4.1.1", + "duplexify": "^4.1.1", + "help-me": "^3.0.0", + "inherits": "^2.0.3", + "lru-cache": "^6.0.0", + "minimist": "^1.2.5", + "mqtt-packet": "^6.8.0", + "number-allocator": "^1.0.9", + "pump": "^3.0.0", + "readable-stream": "^3.6.0", + "reinterval": "^1.1.0", + "rfdc": "^1.3.0", + "split2": "^3.1.0", + "ws": "^7.5.5", + "xtend": "^4.0.2" + }, + "bin": { + "mqtt": "bin/mqtt.js", + "mqtt_pub": "bin/pub.js", + "mqtt_sub": "bin/sub.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mqtt-browser": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/mqtt-browser/-/mqtt-browser-4.3.7.tgz", + "integrity": "sha512-4pxHxa3avIILr2CXhTKlArVpATqfyTu4zr5u2PoUwzgw0GDr5dpzZ0pmPgZyOoQBVgrVDEboCzb/b1Q0yWOm7g==", + "dependencies": { + "mqtt": "4.3.7" + } + }, + "node_modules/mqtt-packet": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-6.10.0.tgz", + "integrity": "sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA==", + "dependencies": { + "bl": "^4.0.2", + "debug": "^4.1.1", + "process-nextick-args": "^2.0.1" + } + }, + "node_modules/mqtt/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mqtt/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/mqtt/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/mrmime": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", @@ -11661,8 +11792,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/multicast-dns": { "version": "7.2.5", @@ -11768,6 +11898,20 @@ "@angular/forms": ">=14.0.0" } }, + "node_modules/ngx-mqtt": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/ngx-mqtt/-/ngx-mqtt-17.0.0.tgz", + "integrity": "sha512-54wVMyDOZkpTZEs0rTMWPP1Yz+6q3rRnHzIBnpqnBkDcyMfNrti45C7ijwnEIaPDzQHMOqVrDgh/6C4ocPPLJQ==", + "dependencies": { + "mqtt-browser": "4.3.7", + "mqtt-packet": "^6.10.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=14", + "@angular/core": ">=14" + } + }, "node_modules/nice-napi": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", @@ -12068,6 +12212,15 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/number-allocator": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz", + "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==", + "dependencies": { + "debug": "^4.3.1", + "js-sdsl": "4.3.0" + } + }, "node_modules/nx": { "version": "17.2.8", "resolved": "https://registry.npmjs.org/nx/-/nx-17.2.8.tgz", @@ -12354,7 +12507,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -12726,7 +12878,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -13172,8 +13323,7 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "node_modules/promise-inflight": { "version": "1.0.1", @@ -13229,6 +13379,15 @@ "dev": true, "optional": true }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -13375,7 +13534,6 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -13492,6 +13650,11 @@ "jsesc": "bin/jsesc" } }, + "node_modules/reinterval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz", + "integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -13622,8 +13785,7 @@ "node_modules/rfdc": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", - "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", - "dev": true + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==" }, "node_modules/rimraf": { "version": "3.0.2", @@ -13719,7 +13881,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -14376,6 +14537,14 @@ "wbuf": "^1.7.3" } }, + "node_modules/split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dependencies": { + "readable-stream": "^3.0.0" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -14403,6 +14572,11 @@ "node": ">= 0.6" } }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==" + }, "node_modules/streamroller": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", @@ -14453,7 +14627,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -14995,6 +15168,11 @@ "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", "dev": true }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, "node_modules/typescript": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", @@ -15170,8 +15348,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/utils-merge": { "version": "1.0.1", @@ -16192,8 +16369,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/ws": { "version": "8.17.1", @@ -16216,6 +16392,14 @@ } } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/modules/ui/package.json b/modules/ui/package.json index 7f83fc5f7..aceb9c389 100644 --- a/modules/ui/package.json +++ b/modules/ui/package.json @@ -31,6 +31,7 @@ "@ngrx/effects": "^17.1.1", "@ngrx/store": "^17.0.1", "ngx-mask": "^16.4.2", + "ngx-mqtt": "^17.0.0", "rxjs": "~7.8.0", "tslib": "^2.6.2", "zone.js": "^0.14.4" diff --git a/modules/ui/src/app/app.component.html b/modules/ui/src/app/app.component.html index 38c210251..b1341a58d 100644 --- a/modules/ui/src/app/app.component.html +++ b/modules/ui/src/app/app.component.html @@ -116,6 +116,14 @@

Testrun

"> tune + + + @@ -127,7 +135,8 @@

Testrun

error.devicePortMissed && error.internetPortMissed; else onePortMissed "> - No ports are detected. Please define a valid ones using + No ports detected. Please connect and configure network and device + connections in the Selected port is missing! Please define a valid one using @@ -187,7 +196,8 @@

Testrun

vm.hasConnectionSettings === true && vm.hasDevices && (!vm.systemStatus || vm.systemStatus === StatusOfTestrun.Idle) && - vm.isStatusLoaded === true + vm.isStatusLoaded === true && + !vm.reports.length "> Step 3: Once device is created, you are able to Testrun vm.systemStatus === StatusOfTestrun.InProgress && isRiskAssessmentRoute === false "> - Congratulations, the device is under test now! Do not forget to fill + The device is now being tested. Why not take the time to complete the + device Testrun role="link" class="message-link" >Risk Assessment questionnaire. It is required to complete verification process. + >? @@ -265,6 +276,7 @@

Testrun

mat-button routerLink="{{ route }}" routerLinkActive="app-sidebar-button-active" + [matTooltip]="label" (keydown.enter)="onNavigationClick()"> {{ icon }} diff --git a/modules/ui/src/app/app.component.scss b/modules/ui/src/app/app.component.scss index 20e81c53e..9639f5cc0 100644 --- a/modules/ui/src/app/app.component.scss +++ b/modules/ui/src/app/app.component.scss @@ -208,3 +208,9 @@ app-version { display: flex; justify-content: center; } + +.separator { + width: 1px; + height: 28px; + background-color: $light-grey; +} diff --git a/modules/ui/src/app/app.component.spec.ts b/modules/ui/src/app/app.component.spec.ts index 81e93b4b6..df531c8b7 100644 --- a/modules/ui/src/app/app.component.spec.ts +++ b/modules/ui/src/app/app.component.spec.ts @@ -56,9 +56,11 @@ import { selectHasDevices, selectHasRiskProfiles, selectInterfaces, + selectInternetConnection, selectIsOpenStartTestrun, selectIsOpenWaitSnackBar, selectMenuOpened, + selectReports, selectStatus, selectSystemStatus, } from './store/selectors'; @@ -67,6 +69,11 @@ import { CertificatesComponent } from './pages/certificates/certificates.compone import { of } from 'rxjs'; import { WINDOW } from './providers/window.provider'; import { LiveAnnouncer } from '@angular/cdk/a11y'; +import { HISTORY } from './mocks/reports.mock'; +import { TestRunMqttService } from './services/test-run-mqtt.service'; +import { MOCK_ADAPTERS } from './mocks/settings.mock'; +import { WifiComponent } from './components/wifi/wifi.component'; +import { MatTooltipModule } from '@angular/material/tooltip'; const windowMock = { location: { @@ -84,6 +91,7 @@ describe('AppComponent', () => { let focusNavigation = true; let mockFocusManagerService: SpyObj; let mockLiveAnnouncer: SpyObj; + let mockMqttService: SpyObj; const enterKeyEvent = new KeyboardEvent('keydown', { key: 'Enter', @@ -109,6 +117,7 @@ describe('AppComponent', () => { 'testrunInProgress', 'fetchProfiles', 'fetchCertificates', + 'getHistory', ]); mockService.fetchCertificates.and.returnValue(of([])); @@ -116,6 +125,7 @@ describe('AppComponent', () => { 'focusFirstElementInContainer', ]); mockLiveAnnouncer = jasmine.createSpyObj('mockLiveAnnouncer', ['announce']); + mockMqttService = jasmine.createSpyObj(['getNetworkAdapters']); TestBed.configureTestingModule({ imports: [ @@ -131,10 +141,13 @@ describe('AppComponent', () => { CalloutComponent, MatIconTestingModule, CertificatesComponent, + WifiComponent, + MatTooltipModule, ], providers: [ { provide: TestRunService, useValue: mockService }, { provide: LiveAnnouncer, useValue: mockLiveAnnouncer }, + { provide: TestRunMqttService, useValue: mockMqttService }, { provide: State, useValue: { @@ -151,6 +164,7 @@ describe('AppComponent', () => { selectors: [ { selector: selectInterfaces, value: {} }, { selector: selectHasConnectionSettings, value: true }, + { selector: selectInternetConnection, value: true }, { selector: selectError, value: null }, { selector: selectMenuOpened, value: false }, { selector: selectHasDevices, value: false }, @@ -159,6 +173,7 @@ describe('AppComponent', () => { { selector: selectSystemStatus, value: null }, { selector: selectIsOpenStartTestrun, value: false }, { selector: selectIsOpenWaitSnackBar, value: false }, + { selector: selectReports, value: [] }, ], }), { provide: FocusManagerService, useValue: mockFocusManagerService }, @@ -173,6 +188,7 @@ describe('AppComponent', () => { ], }); + mockMqttService.getNetworkAdapters.and.returnValue(of(MOCK_ADAPTERS)); store = TestBed.inject(MockStore); fixture = TestBed.createComponent(AppComponent); component = fixture.componentInstance; @@ -429,6 +445,13 @@ describe('AppComponent', () => { expect(version).toBeTruthy(); }); + it('should internet icon', () => { + fixture.detectChanges(); + const internet = compiled.querySelector('app-wifi'); + + expect(internet).toBeTruthy(); + }); + describe('Callout component visibility', () => { describe('with no connection settings', () => { beforeEach(() => { @@ -486,6 +509,48 @@ describe('AppComponent', () => { expect(callout).toBeTruthy(); expect(calloutContent).toContain('Step 3'); }); + + it('should NOT have callout component with "Step 3" if has reports', () => { + store.overrideSelector(selectReports, [...HISTORY]); + store.refreshState(); + fixture.detectChanges(); + + const callout = compiled.querySelector('app-callout'); + + expect(callout).toBeFalsy(); + }); + }); + + describe('with systemStatus data IN Progress and without riskProfiles', () => { + beforeEach(() => { + store.overrideSelector(selectHasConnectionSettings, true); + store.overrideSelector(selectHasDevices, true); + store.overrideSelector(selectHasRiskProfiles, false); + store.overrideSelector( + selectStatus, + MOCK_PROGRESS_DATA_IN_PROGRESS.status + ); + fixture.detectChanges(); + }); + + it('should have callout component with "The device is now being tested" text', () => { + const callout = compiled.querySelector('app-callout'); + const calloutContent = callout?.innerHTML.trim(); + + expect(callout).toBeTruthy(); + expect(calloutContent).toContain('The device is now being tested'); + }); + + it('should have callout component with "Risk Assessment" link', () => { + const callout = compiled.querySelector('app-callout'); + const calloutLinkEl = compiled.querySelector( + '.message-link' + ) as HTMLAnchorElement; + const calloutLinkContent = calloutLinkEl.innerHTML.trim(); + + expect(callout).toBeTruthy(); + expect(calloutLinkContent).toContain('Risk Assessment'); + }); }); describe('with systemStatus data IN Progress and without riskProfiles', () => { @@ -500,12 +565,12 @@ describe('AppComponent', () => { fixture.detectChanges(); }); - it('should have callout component with "Congratulations" text', () => { + it('should have callout component with "The device is now being tested" text', () => { const callout = compiled.querySelector('app-callout'); const calloutContent = callout?.innerHTML.trim(); expect(callout).toBeTruthy(); - expect(calloutContent).toContain('Congratulations'); + expect(calloutContent).toContain('The device is now being tested'); }); it('should have callout component with "Risk Assessment" link', () => { @@ -686,7 +751,7 @@ describe('AppComponent', () => { const calloutContent = callout?.innerHTML.trim(); expect(callout).toBeTruthy(); - expect(calloutContent).toContain('No ports are detected.'); + expect(calloutContent).toContain('No ports detected.'); }); }); diff --git a/modules/ui/src/app/app.component.ts b/modules/ui/src/app/app.component.ts index 341f6bab5..2214b8927 100644 --- a/modules/ui/src/app/app.component.ts +++ b/modules/ui/src/app/app.component.ts @@ -80,6 +80,9 @@ export class AppComponent { this.appStore.getDevices(); this.appStore.getRiskProfiles(); this.appStore.getSystemStatus(); + this.appStore.getReports(); + this.appStore.getTestModules(); + this.appStore.getNetworkAdapters(); this.matIconRegistry.addSvgIcon( 'devices', this.domSanitizer.bypassSecurityTrustResourceUrl(DEVICES_LOGO_URL) diff --git a/modules/ui/src/app/app.module.ts b/modules/ui/src/app/app.module.ts index 78621a464..795d4e0d8 100644 --- a/modules/ui/src/app/app.module.ts +++ b/modules/ui/src/app/app.module.ts @@ -49,6 +49,14 @@ import { ShutdownAppComponent } from './components/shutdown-app/shutdown-app.com import { WindowProvider } from './providers/window.provider'; import { CertificatesComponent } from './pages/certificates/certificates.component'; import { LOADER_TIMEOUT_CONFIG_TOKEN } from './services/loaderConfig'; +import { WifiComponent } from './components/wifi/wifi.component'; + +import { MqttModule, IMqttServiceOptions } from 'ngx-mqtt'; + +export const MQTT_SERVICE_OPTIONS: IMqttServiceOptions = { + hostname: 'localhost', + port: 9001, +}; @NgModule({ declarations: [AppComponent, SettingsComponent], @@ -79,6 +87,8 @@ import { LOADER_TIMEOUT_CONFIG_TOKEN } from './services/loaderConfig'; SettingsDropdownComponent, ShutdownAppComponent, CertificatesComponent, + MqttModule.forRoot(MQTT_SERVICE_OPTIONS), + WifiComponent, ], providers: [ WindowProvider, diff --git a/modules/ui/src/app/app.store.spec.ts b/modules/ui/src/app/app.store.spec.ts index 2bdf63195..e26db7eb3 100644 --- a/modules/ui/src/app/app.store.spec.ts +++ b/modules/ui/src/app/app.store.spec.ts @@ -24,22 +24,30 @@ import { selectHasDevices, selectHasRiskProfiles, selectInterfaces, + selectInternetConnection, selectIsOpenWaitSnackBar, selectMenuOpened, + selectReports, selectStatus, + selectTestModules, } from './store/selectors'; import { TestRunService } from './services/test-run.service'; import SpyObj = jasmine.SpyObj; -import { device } from './mocks/device.mock'; +import { device, MOCK_MODULES, MOCK_TEST_MODULES } from './mocks/device.mock'; import { + fetchReports, fetchRiskProfiles, fetchSystemStatus, setDevices, + updateAdapters, + setTestModules, } from './store/actions'; import { MOCK_PROGRESS_DATA_IN_PROGRESS } from './mocks/testrun.mock'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { NotificationService } from './services/notification.service'; import { FocusManagerService } from './services/focus-manager.service'; +import { TestRunMqttService } from './services/test-run-mqtt.service'; +import { MOCK_ADAPTERS } from './mocks/settings.mock'; const mock = (() => { let store: { [key: string]: string } = {}; @@ -65,15 +73,20 @@ describe('AppStore', () => { let mockService: SpyObj; let mockNotificationService: SpyObj; let mockFocusManagerService: SpyObj; + let mockMqttService: SpyObj; beforeEach(() => { - mockService = jasmine.createSpyObj('mockService', ['fetchDevices']); + mockService = jasmine.createSpyObj('mockService', [ + 'fetchDevices', + 'getTestModules', + ]); mockNotificationService = jasmine.createSpyObj('mockNotificationService', [ 'notify', ]); mockFocusManagerService = jasmine.createSpyObj([ 'focusFirstElementInContainer', ]); + mockMqttService = jasmine.createSpyObj(['getNetworkAdapters']); TestBed.configureTestingModule({ providers: [ @@ -82,11 +95,14 @@ describe('AppStore', () => { selectors: [ { selector: selectStatus, value: null }, { selector: selectIsOpenWaitSnackBar, value: false }, + { selector: selectTestModules, value: MOCK_TEST_MODULES }, + { selector: selectInternetConnection, value: false }, ], }), { provide: TestRunService, useValue: mockService }, { provide: NotificationService, useValue: mockNotificationService }, { provide: FocusManagerService, useValue: mockFocusManagerService }, + { provide: TestRunMqttService, useValue: mockMqttService }, ], imports: [BrowserAnimationsModule], }); @@ -96,6 +112,7 @@ describe('AppStore', () => { store.overrideSelector(selectHasDevices, true); store.overrideSelector(selectHasRiskProfiles, false); + store.overrideSelector(selectReports, []); store.overrideSelector(selectHasConnectionSettings, true); store.overrideSelector(selectMenuOpened, true); store.overrideSelector(selectInterfaces, {}); @@ -140,12 +157,14 @@ describe('AppStore', () => { consentShown: false, hasDevices: true, hasRiskProfiles: false, + reports: [], isStatusLoaded: false, systemStatus: null, hasConnectionSettings: true, isMenuOpen: true, interfaces: {}, settingMissedError: null, + hasInternetConnection: false, }); done(); }); @@ -226,5 +245,66 @@ describe('AppStore', () => { ).toHaveBeenCalled(); })); }); + + describe('getReports', () => { + it('should dispatch fetchReports', () => { + appStore.getReports(); + + expect(store.dispatch).toHaveBeenCalledWith(fetchReports()); + }); + }); + + describe('getTestModules', () => { + const modules = [...MOCK_MODULES]; + + beforeEach(() => { + mockService.getTestModules.and.returnValue(of(modules)); + }); + + it('should dispatch action setDevices', () => { + appStore.getTestModules(); + + expect(store.dispatch).toHaveBeenCalledWith( + setTestModules({ + testModules: [ + { + displayName: 'Connection', + name: 'connection', + enabled: true, + }, + { + displayName: 'Udmi', + name: 'udmi', + enabled: true, + }, + ], + }) + ); + }); + }); + + describe('getNetworkAdapters', () => { + const adapters = MOCK_ADAPTERS; + + beforeEach(() => { + mockMqttService.getNetworkAdapters.and.returnValue(of(adapters)); + }); + + it('should dispatch action setDevices', () => { + appStore.getNetworkAdapters(); + + expect(store.dispatch).toHaveBeenCalledWith( + updateAdapters({ adapters }) + ); + }); + + it('should notify about new adapters', () => { + appStore.getNetworkAdapters(); + + expect(mockNotificationService.notify).toHaveBeenCalledWith( + 'New network adapter(s) mockNewInternetKey has been detected. You can switch to using it in the System settings menu' + ); + }); + }); }); }); diff --git a/modules/ui/src/app/app.store.ts b/modules/ui/src/app/app.store.ts index 9bd8dcff4..6e338968f 100644 --- a/modules/ui/src/app/app.store.ts +++ b/modules/ui/src/app/app.store.ts @@ -23,23 +23,34 @@ import { selectHasDevices, selectHasRiskProfiles, selectInterfaces, + selectInternetConnection, selectMenuOpened, + selectReports, selectStatus, } from './store/selectors'; import { Store } from '@ngrx/store'; import { AppState } from './store/state'; import { TestRunService } from './services/test-run.service'; import { delay, exhaustMap, Observable, skip } from 'rxjs'; -import { Device } from './model/device'; +import { Device, TestModule } from './model/device'; import { setDevices, setIsOpenStartTestrun, fetchSystemStatus, fetchRiskProfiles, + fetchReports, + setTestModules, + updateAdapters, } from './store/actions'; import { TestrunStatus } from './model/testrun-status'; -import { SettingMissedError, SystemInterfaces } from './model/setting'; +import { + Adapters, + SettingMissedError, + SystemInterfaces, +} from './model/setting'; import { FocusManagerService } from './services/focus-manager.service'; +import { TestRunMqttService } from './services/test-run-mqtt.service'; +import { NotificationService } from './services/notification.service'; export const CONSENT_SHOWN_KEY = 'CONSENT_SHOWN'; export interface AppComponentState { @@ -51,8 +62,10 @@ export interface AppComponentState { export class AppStore extends ComponentStore { private consentShown$ = this.select(state => state.consentShown); private isStatusLoaded$ = this.select(state => state.isStatusLoaded); + private hasInternetConnection$ = this.store.select(selectInternetConnection); private hasDevices$ = this.store.select(selectHasDevices); private hasRiskProfiles$ = this.store.select(selectHasRiskProfiles); + private reports$ = this.store.select(selectReports); private hasConnectionSetting$ = this.store.select( selectHasConnectionSettings ); @@ -67,12 +80,14 @@ export class AppStore extends ComponentStore { consentShown: this.consentShown$, hasDevices: this.hasDevices$, hasRiskProfiles: this.hasRiskProfiles$, + reports: this.reports$, isStatusLoaded: this.isStatusLoaded$, systemStatus: this.systemStatus$, hasConnectionSettings: this.hasConnectionSetting$, isMenuOpen: this.isMenuOpen$, interfaces: this.interfaces$, settingMissedError: this.settingMissedError$, + hasInternetConnection: this.hasInternetConnection$, }); updateConsent = this.updater((state, consentShown: boolean) => ({ @@ -131,6 +146,27 @@ export class AppStore extends ComponentStore { ); }); + getNetworkAdapters = this.effect(trigger$ => { + return trigger$.pipe( + exhaustMap(() => { + return this.testRunMqttService.getNetworkAdapters().pipe( + tap((adapters: Adapters) => { + if (adapters.adapters_added) { + this.notifyAboutTheAdapters(adapters.adapters_added); + } + this.store.dispatch(updateAdapters({ adapters })); + }) + ); + }) + ); + }); + + private notifyAboutTheAdapters(adapters: SystemInterfaces) { + this.notificationService.notify( + `New network adapter(s) ${Object.keys(adapters).join(', ')} has been detected. You can switch to using it in the System settings menu` + ); + } + setIsOpenStartTestrun = this.effect(trigger$ => { return trigger$.pipe( tap(() => { @@ -150,10 +186,43 @@ export class AppStore extends ComponentStore { ); }); + getReports = this.effect(trigger$ => { + return trigger$.pipe( + tap(() => { + this.store.dispatch(fetchReports()); + }) + ); + }); + + getTestModules = this.effect(trigger$ => { + return trigger$.pipe( + exhaustMap(() => { + return this.testRunService.getTestModules().pipe( + tap((testModules: string[]) => { + this.store.dispatch( + setTestModules({ + testModules: testModules.map( + module => + ({ + displayName: module, + name: module.toLowerCase(), + enabled: true, + }) as TestModule + ), + }) + ); + }) + ); + }) + ); + }); + constructor( private store: Store, private testRunService: TestRunService, - private focusManagerService: FocusManagerService + private testRunMqttService: TestRunMqttService, + private focusManagerService: FocusManagerService, + private notificationService: NotificationService ) { super({ consentShown: sessionStorage.getItem(CONSENT_SHOWN_KEY) !== null, diff --git a/modules/ui/src/app/components/download-report-zip/download-report-zip.component.spec.ts b/modules/ui/src/app/components/download-report-zip/download-report-zip.component.spec.ts index d87200987..2b2fe7994 100644 --- a/modules/ui/src/app/components/download-report-zip/download-report-zip.component.spec.ts +++ b/modules/ui/src/app/components/download-report-zip/download-report-zip.component.spec.ts @@ -171,9 +171,9 @@ describe('DownloadReportZipComponent', () => { expect(spyOnShow).toHaveBeenCalled(); }); - it('should be shown on focusin', () => { + it('should be shown on keyup', () => { const spyOnShow = spyOn(component.tooltip, 'show'); - fixture.nativeElement.dispatchEvent(new Event('focusin')); + fixture.nativeElement.dispatchEvent(new Event('keyup')); expect(spyOnShow).toHaveBeenCalled(); }); @@ -185,9 +185,9 @@ describe('DownloadReportZipComponent', () => { expect(spyOnHide).toHaveBeenCalled(); }); - it('should be hidden on focusout', () => { + it('should be hidden on keydown', () => { const spyOnHide = spyOn(component.tooltip, 'hide'); - fixture.nativeElement.dispatchEvent(new Event('focusout')); + fixture.nativeElement.dispatchEvent(new Event('keydown')); expect(spyOnHide).toHaveBeenCalled(); }); diff --git a/modules/ui/src/app/components/download-report-zip/download-report-zip.component.ts b/modules/ui/src/app/components/download-report-zip/download-report-zip.component.ts index e7b106f05..d5b4b41ce 100644 --- a/modules/ui/src/app/components/download-report-zip/download-report-zip.component.ts +++ b/modules/ui/src/app/components/download-report-zip/download-report-zip.component.ts @@ -87,13 +87,13 @@ export class DownloadReportZipComponent readonly tabIndex = 0; @HostListener('mouseenter') - @HostListener('focusin', ['$event']) + @HostListener('keyup', ['$event']) onEvent(): void { this.tooltip.show(); } @HostListener('mouseleave') - @HostListener('focusout', ['$event']) + @HostListener('keydown', ['$event']) outEvent(): void { this.tooltip.hide(); } diff --git a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.html b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.html index b3dfb77f4..2e9446cfa 100644 --- a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.html +++ b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.html @@ -14,21 +14,59 @@ limitations under the License. --> Download ZIP file -

- Risk profile is required for device verification. Please, consider creating a - Risk assessment profile for your ZIP report. +

+ Risk Profile is required for device verification. Please consider going to + Risk Assessment + and creating a profile to attach to your report.

-
+

+ Risk Profile is required for device verification. Please select a profile from + the list, or go to + Risk Assessment + and create a new one to attach to your report. +

+ +
+ aria-label="Please choose a Risk Profile from the list"> - {{ selectedProfile }} + {{ selectedProfile.name }} + + {{ selectedProfile.risk }} risk + - +
- Please choose risk assessment profile + Please choose a Risk Profile from the list -
- - - - - - diff --git a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.scss b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.scss index 3524cb936..0a92617c1 100644 --- a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.scss +++ b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.scss @@ -53,10 +53,6 @@ padding: 16px 0 0; } -.risk-profile-select-form-actions button:first-child { - margin-right: auto; -} - .profile-select { width: 100%; } @@ -69,3 +65,22 @@ font-size: 12px; color: $grey-700; } + +.redirect-link { + cursor: pointer; + color: $primary; + display: inline-block; + width: fit-content; +} + +::ng-deep mat-select-trigger { + display: inline-flex; + width: 100%; + justify-content: space-between; +} + +::ng-deep mat-select-trigger .profile-item-risk { + vertical-align: middle; + align-self: center; + margin-right: 16px; +} diff --git a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.spec.ts b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.spec.ts index cdd4c665f..728590ef8 100644 --- a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.spec.ts +++ b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.spec.ts @@ -59,23 +59,19 @@ describe('DownloadZipModalComponent', () => { expect(select).toBeTruthy(); }); - it('should preselect first profile', async () => { - const select = fixture.nativeElement.querySelector( - 'mat-select' - ) as HTMLElement; - - expect(select.getAttribute('ng-reflect-value')).toEqual( - 'Primary profile' + it('should preselect "no profile" option', async () => { + expect(component.selectedProfile.name).toEqual( + 'No Risk Profile selected' ); }); it('should close with null on redirect button click', async () => { const closeSpy = spyOn(component.dialogRef, 'close'); - const redirectButton = fixture.nativeElement.querySelector( - '.redirect-button' - ) as HTMLButtonElement; + const redirectLink = fixture.nativeElement.querySelector( + '.redirect-link' + ) as HTMLAnchorElement; - redirectButton.click(); + redirectLink.click(); expect(closeSpy).toHaveBeenCalledWith(null); @@ -103,13 +99,17 @@ describe('DownloadZipModalComponent', () => { downloadButton.click(); - expect(closeSpy).toHaveBeenCalledWith('Primary profile'); + expect(closeSpy).toHaveBeenCalledWith(''); closeSpy.calls.reset(); }); it('should have filtered and sorted profiles', async () => { - expect(component.profiles).toEqual([PROFILE_MOCK, PROFILE_MOCK_2]); + expect(component.profiles).toEqual([ + component.NO_PROFILE, + PROFILE_MOCK, + PROFILE_MOCK_2, + ]); }); it('#getRiskClass should call the service method getRiskClass"', () => { @@ -141,19 +141,19 @@ describe('DownloadZipModalComponent', () => { fixture.detectChanges(); }); - it('should have no dropdown with profiles', async () => { + it('should have disabled dropdown', async () => { const select = fixture.nativeElement.querySelector('mat-select'); - expect(select).toEqual(null); + expect(select.classList.contains('mat-mdc-select-disabled')).toBeTruthy(); }); it('should close with null on redirect button click', async () => { const closeSpy = spyOn(component.dialogRef, 'close'); - const redirectButton = fixture.nativeElement.querySelector( - '.redirect-button' - ) as HTMLButtonElement; + const redirectLink = fixture.nativeElement.querySelector( + '.redirect-link' + ) as HTMLAnchorElement; - redirectButton.click(); + redirectLink.click(); expect(closeSpy).toHaveBeenCalledWith(null); diff --git a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts index 395bcb480..b042bdabf 100644 --- a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts +++ b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts @@ -17,6 +17,9 @@ import { MatFormField } from '@angular/material/form-field'; import { MatSelectModule } from '@angular/material/select'; import { MatOptionModule } from '@angular/material/core'; import { TestRunService } from '../../services/test-run.service'; +import { Routes } from '../../model/routes'; +import { RouterLink } from '@angular/router'; +import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip'; interface DialogData { profiles: Profile[]; @@ -35,14 +38,22 @@ interface DialogData { MatFormField, MatSelectModule, MatOptionModule, + RouterLink, + MatTooltip, + MatTooltipModule, ], templateUrl: './download-zip-modal.component.html', styleUrl: './download-zip-modal.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) export class DownloadZipModalComponent extends EscapableDialogComponent { + readonly NO_PROFILE = { + name: 'No Risk Profile selected', + questions: [], + } as Profile; + public readonly Routes = Routes; profiles: Profile[] = []; - selectedProfile: string = ''; + selectedProfile: Profile; constructor( private readonly testRunService: TestRunService, public override dialogRef: MatDialogRef, @@ -56,12 +67,20 @@ export class DownloadZipModalComponent extends EscapableDialogComponent { this.profiles.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()) ); - this.selectedProfile = this.profiles[0].name; } + this.profiles.unshift(this.NO_PROFILE); + this.selectedProfile = this.profiles[0]; } - cancel(profile?: string | null) { - this.dialogRef.close(profile); + cancel(profile?: Profile | null) { + if (profile === null) { + this.dialogRef.close(null); + } + let value = profile?.name; + if (profile && profile?.name === this.NO_PROFILE.name) { + value = ''; + } + this.dialogRef.close(value); } public getRiskClass(riskResult: string): RiskResultClassName { diff --git a/modules/ui/src/app/components/snack-bar/snack-bar.component.html b/modules/ui/src/app/components/snack-bar/snack-bar.component.html index 716198299..539623d4b 100644 --- a/modules/ui/src/app/components/snack-bar/snack-bar.component.html +++ b/modules/ui/src/app/components/snack-bar/snack-bar.component.html @@ -15,9 +15,10 @@ -->
-

The Waiting for Device stage is taking more than one minute.

+

It is taking longer than expected to find your device on the network.

- Please check device connection or stop and update system configuration. + Please check the connection to the device or stop and update your system + configuration.

diff --git a/modules/ui/src/app/components/wifi/wifi.component.html b/modules/ui/src/app/components/wifi/wifi.component.html new file mode 100644 index 000000000..c93d05f7e --- /dev/null +++ b/modules/ui/src/app/components/wifi/wifi.component.html @@ -0,0 +1,25 @@ + + diff --git a/modules/ui/src/app/components/wifi/wifi.component.scss b/modules/ui/src/app/components/wifi/wifi.component.scss new file mode 100644 index 000000000..bc0ac542e --- /dev/null +++ b/modules/ui/src/app/components/wifi/wifi.component.scss @@ -0,0 +1,40 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import '../../../theming/colors'; + +$icon-size: 24px; + +.app-toolbar-button { + border-radius: 20px; + border: 1px solid transparent; + min-width: 48px; + padding: 0; + box-sizing: border-box; + height: 34px; + margin: 11px 0; + line-height: 50% !important; + &.disabled { + opacity: 0.6; + } +} + +.wifi-icon { + margin-right: 0; + width: $icon-size; + font-size: $icon-size; + color: $dark-grey; + height: $icon-size; +} diff --git a/modules/ui/src/app/components/wifi/wifi.component.spec.ts b/modules/ui/src/app/components/wifi/wifi.component.spec.ts new file mode 100644 index 000000000..55e85a6a7 --- /dev/null +++ b/modules/ui/src/app/components/wifi/wifi.component.spec.ts @@ -0,0 +1,100 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WifiComponent } from './wifi.component'; + +describe('WifiComponent', () => { + let component: WifiComponent; + let fixture: ComponentFixture; + let compiled: HTMLElement; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [WifiComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(WifiComponent); + component = fixture.componentInstance; + compiled = fixture.nativeElement as HTMLElement; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('Class tests', () => { + describe('with internet connection', () => { + it('should return label', () => { + expect(component.getLabel(true)).toEqual( + 'Testrun detects a working internet connection for the device under test.' + ); + }); + }); + + describe('with no internet connection', () => { + it('should return label', () => { + expect(component.getLabel(false)).toEqual( + 'No internet connection detected for the device under test.' + ); + }); + }); + + describe('with N/A internet connection', () => { + it('should return label', () => { + expect(component.getLabel(false, true)).toEqual( + 'Internet connection is not being monitored.' + ); + }); + }); + }); + + describe('DOM tests', () => { + describe('with internet connection', () => { + it('should have wifi icon', () => { + component.on = true; + fixture.detectChanges(); + + const icon = compiled.querySelector('mat-icon')?.textContent?.trim(); + + expect(icon).toEqual('wifi'); + }); + }); + + describe('should have no wifi icon', () => { + it('should have no wifi icon', () => { + component.on = false; + fixture.detectChanges(); + + const icon = compiled.querySelector('mat-icon')?.textContent?.trim(); + + expect(icon).toEqual('wifi_off'); + }); + }); + + it('button should be disabled', () => { + component.disable = true; + fixture.detectChanges(); + + const shutdownButton = compiled.querySelector( + '.wifi-button' + ) as HTMLButtonElement; + + expect(shutdownButton?.classList.contains('disabled')).toBeTrue(); + }); + }); +}); diff --git a/modules/ui/src/app/components/wifi/wifi.component.ts b/modules/ui/src/app/components/wifi/wifi.component.ts new file mode 100644 index 000000000..e7e28e8f9 --- /dev/null +++ b/modules/ui/src/app/components/wifi/wifi.component.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Component, Input } from '@angular/core'; +import { MatIcon } from '@angular/material/icon'; +import { MatTooltip } from '@angular/material/tooltip'; +import { MatButton, MatIconButton } from '@angular/material/button'; + +@Component({ + selector: 'app-wifi', + standalone: true, + imports: [MatIcon, MatTooltip, MatButton, MatIconButton], + templateUrl: './wifi.component.html', + styleUrl: './wifi.component.scss', +}) +export class WifiComponent { + @Input() on: boolean | null = null; + @Input() disable: boolean = false; + + getLabel(on: boolean | null, disable: boolean = false) { + if (disable) { + return 'Internet connection is not being monitored.'; + } + return on + ? 'Testrun detects a working internet connection for the device under test.' + : 'No internet connection detected for the device under test.'; + } +} diff --git a/modules/ui/src/app/interceptors/error.interceptor.spec.ts b/modules/ui/src/app/interceptors/error.interceptor.spec.ts index 9fff32863..7271223a2 100644 --- a/modules/ui/src/app/interceptors/error.interceptor.spec.ts +++ b/modules/ui/src/app/interceptors/error.interceptor.spec.ts @@ -42,11 +42,15 @@ describe('ErrorInterceptor', () => { interceptor = TestBed.inject(ErrorInterceptor); }); + afterEach(() => { + notificationServiceMock.notify.calls.reset(); + }); + it('should be created', () => { expect(interceptor).toBeTruthy(); }); - it('should notify about backend errors', done => { + it('should notify about backend errors with message if exist', done => { const next: HttpHandler = { handle: () => { return throwError( @@ -66,6 +70,26 @@ describe('ErrorInterceptor', () => { ); }); + it('should notify about backend errors with default message', done => { + const next: HttpHandler = { + handle: () => { + return throwError(new HttpErrorResponse({ status: 500 })); + }, + }; + + const requestMock = new HttpRequest('GET', '/test'); + + interceptor.intercept(requestMock, next).subscribe( + () => ({}), + () => { + expect(notificationServiceMock.notify).toHaveBeenCalledWith( + 'Something went wrong. Check the Terminal for details.' + ); + done(); + } + ); + }); + it('should notify about other errors', done => { const next: HttpHandler = { handle: () => { @@ -79,7 +103,7 @@ describe('ErrorInterceptor', () => { () => ({}), () => { expect(notificationServiceMock.notify).toHaveBeenCalledWith( - 'Back End is not responding. Please, try again later.' + 'Testrun is not responding. Please try again in a moment.' ); done(); } @@ -99,7 +123,7 @@ describe('ErrorInterceptor', () => { () => ({}), () => { expect(notificationServiceMock.notify).toHaveBeenCalledWith( - 'Back End is not responding. Please, try again later.' + 'Testrun is not responding. Please try again in a moment.' ); done(); } diff --git a/modules/ui/src/app/interceptors/error.interceptor.ts b/modules/ui/src/app/interceptors/error.interceptor.ts index 924cbde02..9e653895a 100644 --- a/modules/ui/src/app/interceptors/error.interceptor.ts +++ b/modules/ui/src/app/interceptors/error.interceptor.ts @@ -57,17 +57,19 @@ export class ErrorInterceptor implements HttpInterceptor { catchError((error: HttpErrorResponse | TimeoutError) => { if (error instanceof TimeoutError) { this.notificationService.notify( - 'Back End is not responding. Please, try again later.' + 'Testrun is not responding. Please try again in a moment.' ); } else { if (error.status === 0) { this.notificationService.notify( - 'Back End is not responding. Please, try again later.' + 'Testrun is not responding. Please try again in a moment.' ); } else { this.notificationService.notify( - error.error?.error || error.message + error.error?.error || + 'Something went wrong. Check the Terminal for details.' ); + console.error(error.error?.error || error.message); } } return throwError(error); diff --git a/modules/ui/src/app/mocks/device.mock.ts b/modules/ui/src/app/mocks/device.mock.ts index 6066593e6..8bbfb56ea 100644 --- a/modules/ui/src/app/mocks/device.mock.ts +++ b/modules/ui/src/app/mocks/device.mock.ts @@ -43,8 +43,10 @@ export const MOCK_TEST_MODULES = [ enabled: true, }, { - displayName: 'Smart Ready', + displayName: 'Udmi', name: 'udmi', enabled: false, }, ]; + +export const MOCK_MODULES = ['Connection', 'Udmi']; diff --git a/modules/ui/src/app/mocks/profile.mock.ts b/modules/ui/src/app/mocks/profile.mock.ts index 3715685cd..d53703809 100644 --- a/modules/ui/src/app/mocks/profile.mock.ts +++ b/modules/ui/src/app/mocks/profile.mock.ts @@ -155,3 +155,57 @@ export const RENAME_PROFILE_MOCK = { name: 'Primary profile', rename: 'New profile', }; + +export const COPY_PROFILE_MOCK: Profile = { + name: 'Copy of Primary profile', + status: ProfileStatus.VALID, + questions: [ + { + question: 'What is the email of the device owner(s)?', + answer: 'boddey@google.com, cmeredith@google.com', + }, + { + question: 'What type of device do you need reviewed?', + answer: 'IoT Sensor', + }, + { + question: 'Are any of the following statements true about your device?', + answer: 'First', + }, + { + question: 'What features does the device have?', + answer: [0, 1, 2], + }, + { + question: 'Comments', + answer: 'Yes', + }, + ], +}; + +export const OUTDATED_DRAFT_PROFILE_MOCK: Profile = { + name: 'Outdated profile', + status: ProfileStatus.DRAFT, + questions: [ + { + question: 'Old question', + answer: 'qwerty', + }, + { + question: 'What is the email of the device owner(s)?', + answer: 'boddey@google.com, cmeredith@google.com', + }, + { + question: 'What type of device do you need reviewed?', + answer: 'IoT Sensor', + }, + { + question: 'Another old question', + answer: 'qwerty', + }, + ], +}; + +export const EXPIRED_PROFILE_MOCK: Profile = Object.assign({}, PROFILE_MOCK, { + status: ProfileStatus.EXPIRED, +}); diff --git a/modules/ui/src/app/mocks/reports.mock.ts b/modules/ui/src/app/mocks/reports.mock.ts index 0cfb39420..e1422a36c 100644 --- a/modules/ui/src/app/mocks/reports.mock.ts +++ b/modules/ui/src/app/mocks/reports.mock.ts @@ -28,6 +28,19 @@ export const HISTORY = [ started: '2023-07-23T10:11:00.123Z', finished: '2023-07-23T10:17:10.123Z', }, + { + mac_addr: null, + status: 'compliant', + device: { + manufacturer: 'Delta', + model: '03-DIN-SRC', + mac_addr: '01:02:03:04:05:08', + firmware: '1.2.2', + }, + report: 'https://api.testrun.io/report.pdf', + started: '2023-06-23T10:11:00.123Z', + finished: '2023-06-23T10:17:10.123Z', + }, ] as TestrunStatus[]; export const HISTORY_AFTER_REMOVE = [ @@ -43,9 +56,19 @@ export const HISTORY_AFTER_REMOVE = [ report: 'https://api.testrun.io/report.pdf', started: '2023-06-23T10:11:00.123Z', finished: '2023-06-23T10:17:10.123Z', - deviceFirmware: '1.2.2', - deviceInfo: 'Delta 03-DIN-SRC', - duration: '06m 10s', + }, + { + mac_addr: null, + status: 'compliant', + device: { + manufacturer: 'Delta', + model: '03-DIN-SRC', + mac_addr: '01:02:03:04:05:08', + firmware: '1.2.2', + }, + report: 'https://api.testrun.io/report.pdf', + started: '2023-06-23T10:11:00.123Z', + finished: '2023-06-23T10:17:10.123Z', }, ]; @@ -82,6 +105,22 @@ export const FORMATTED_HISTORY = [ deviceInfo: 'Delta 03-DIN-SRC', duration: '06m 10s', }, + { + mac_addr: null, + status: 'compliant', + device: { + manufacturer: 'Delta', + model: '03-DIN-SRC', + mac_addr: '01:02:03:04:05:08', + firmware: '1.2.2', + }, + report: 'https://api.testrun.io/report.pdf', + started: '2023-06-23T10:11:00.123Z', + finished: '2023-06-23T10:17:10.123Z', + deviceFirmware: '1.2.2', + deviceInfo: 'Delta 03-DIN-SRC', + duration: '06m 10s', + }, ]; export const FILTERS = { diff --git a/modules/ui/src/app/mocks/settings.mock.ts b/modules/ui/src/app/mocks/settings.mock.ts index baab9a2c0..49a11a895 100644 --- a/modules/ui/src/app/mocks/settings.mock.ts +++ b/modules/ui/src/app/mocks/settings.mock.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { SystemConfig, SystemInterfaces } from '../model/setting'; +import { Adapters, SystemConfig, SystemInterfaces } from '../model/setting'; export const MOCK_SYSTEM_CONFIG_WITH_NO_DATA: SystemConfig = { network: { @@ -60,3 +60,8 @@ export const MOCK_PERIOD_VALUE: SystemInterfaces = { key: '600', value: 'Very slow device', }; + +export const MOCK_ADAPTERS: Adapters = { + adapters_added: { mockNewInternetKey: 'mockNewInternetValue' }, + adapters_removed: { mockInternetKey: 'mockInternetValue' }, +}; diff --git a/modules/ui/src/app/mocks/testrun.mock.ts b/modules/ui/src/app/mocks/testrun.mock.ts index 0572e79c0..bb588634c 100644 --- a/modules/ui/src/app/mocks/testrun.mock.ts +++ b/modules/ui/src/app/mocks/testrun.mock.ts @@ -65,7 +65,7 @@ const PROGRESS_DATA_RESPONSE = ( status: string, finished: string | null, tests: TestsData | IResult[], - report?: string + report: string = '' ) => { return { status, diff --git a/modules/ui/src/app/mocks/topic.mock.ts b/modules/ui/src/app/mocks/topic.mock.ts new file mode 100644 index 000000000..4309ae84f --- /dev/null +++ b/modules/ui/src/app/mocks/topic.mock.ts @@ -0,0 +1,5 @@ +import { InternetConnection } from '../model/topic'; + +export const MOCK_INTERNET: InternetConnection = { + connection: false, +}; diff --git a/modules/ui/src/app/model/profile.ts b/modules/ui/src/app/model/profile.ts index efdb779e6..059b3cafe 100644 --- a/modules/ui/src/app/model/profile.ts +++ b/modules/ui/src/app/model/profile.ts @@ -22,11 +22,6 @@ export interface Profile { created?: string; } -export interface Question { - question?: string; - answer?: string | number[]; -} - export enum FormControlType { SELECT = 'select', TEXTAREA = 'text-long', @@ -62,6 +57,7 @@ export enum ProfileRisk { export enum ProfileStatus { VALID = 'Valid', DRAFT = 'Draft', + EXPIRED = 'Expired', } export interface RiskResultClassName { diff --git a/modules/ui/src/app/model/setting.ts b/modules/ui/src/app/model/setting.ts index 5e71052f3..708dcfc94 100644 --- a/modules/ui/src/app/model/setting.ts +++ b/modules/ui/src/app/model/setting.ts @@ -48,3 +48,8 @@ export enum FormKey { export type SystemInterfaces = { [key: string]: string; }; + +export type Adapters = { + adapters_added?: SystemInterfaces; + adapters_removed?: SystemInterfaces; +}; diff --git a/modules/ui/src/app/model/testrun-status.ts b/modules/ui/src/app/model/testrun-status.ts index 2ac908185..3bc63804c 100644 --- a/modules/ui/src/app/model/testrun-status.ts +++ b/modules/ui/src/app/model/testrun-status.ts @@ -16,13 +16,13 @@ import { Device } from './device'; export interface TestrunStatus { - mac_addr: string; + mac_addr: string | null; status: string; device: IDevice; started: string | null; finished: string | null; tests?: TestsResponse; - report?: string; + report: string; } export interface HistoryTestrun extends TestrunStatus { @@ -75,7 +75,9 @@ export enum StatusOfTestResult { NotStarted = 'Not Started', InProgress = 'In Progress', Error = 'Error', // test failed to run - Info = 'Informational', // nice to know information, not necessarily compliant/non-compliant + Info = 'Informational', // nice to know information, not necessarily compliant/non-compliant, + Skipped = 'Skipped', + Disabled = 'Disabled', } export interface StatusResultClassName { @@ -85,6 +87,19 @@ export interface StatusResultClassName { grey: boolean; } +export const IDLE_STATUS = { + status: StatusOfTestrun.Idle, + device: {} as IDevice, + started: null, + finished: null, + report: '', + mac_addr: '', + tests: { + total: 0, + results: [], + }, +} as TestrunStatus; + export type TestrunStatusKey = keyof typeof StatusOfTestrun; export type TestrunStatusValue = (typeof StatusOfTestrun)[TestrunStatusKey]; export type TestResultKey = keyof typeof StatusOfTestResult; diff --git a/modules/ui/src/app/model/topic.ts b/modules/ui/src/app/model/topic.ts new file mode 100644 index 000000000..d330dbb82 --- /dev/null +++ b/modules/ui/src/app/model/topic.ts @@ -0,0 +1,9 @@ +export enum Topic { + NetworkAdapters = 'events/adapter', + InternetConnection = 'events/internet', + Status = 'status', +} + +export interface InternetConnection { + connection: boolean | null; +} diff --git a/modules/ui/src/app/pages/certificates/certificates.store.spec.ts b/modules/ui/src/app/pages/certificates/certificates.store.spec.ts index 06e3accf6..5e66104e6 100644 --- a/modules/ui/src/app/pages/certificates/certificates.store.spec.ts +++ b/modules/ui/src/app/pages/certificates/certificates.store.spec.ts @@ -42,6 +42,8 @@ describe('CertificatesStore', () => { 'uploadCertificate', 'deleteCertificate', ]); + // @ts-expect-error data layer should be defined + window.dataLayer = window.dataLayer || []; TestBed.configureTestingModule({ imports: [NoopAnimationsModule], @@ -152,6 +154,21 @@ describe('CertificatesStore', () => { container ); }); + + it('should send GA event "successful_saving_certificate"', () => { + const container = document.createElement('DIV'); + container.classList.add('certificates-drawer-content'); + document.querySelector('body')?.appendChild(container); + certificateStore.uploadCertificate(FILE); + + expect( + // @ts-expect-error data layer should be defined + window.dataLayer.some( + (item: { event: string }) => + item.event === 'successful_saving_certificate' + ) + ).toBeTruthy(); + }); }); describe('with invalid certificate file', () => { diff --git a/modules/ui/src/app/pages/certificates/certificates.store.ts b/modules/ui/src/app/pages/certificates/certificates.store.ts index 21f96eed0..610daeffb 100644 --- a/modules/ui/src/app/pages/certificates/certificates.store.ts +++ b/modules/ui/src/app/pages/certificates/certificates.store.ts @@ -97,6 +97,10 @@ export class CertificatesStore extends ComponentStore { !certificates.some(cert => cert.name === certificate.name) )[0]; this.updateCertificates(newCertificates); + // @ts-expect-error data layer is not null + window.dataLayer.push({ + event: 'successful_saving_certificate', + }); this.notify( `Certificate successfully added.\n${uploadedCertificate.name} by ${uploadedCertificate.organisation} valid until ${this.datePipe.transform(uploadedCertificate.expires, 'dd MMM yyyy')}` ); diff --git a/modules/ui/src/app/pages/devices/components/device-form/device.validators.ts b/modules/ui/src/app/pages/devices/components/device-form/device.validators.ts index 2b7b23ae1..60ceb5d48 100644 --- a/modules/ui/src/app/pages/devices/components/device-form/device.validators.ts +++ b/modules/ui/src/app/pages/devices/components/device-form/device.validators.ts @@ -22,8 +22,11 @@ import { Device } from '../../../../model/device'; * Validator uses for Device Name and Device Manufacturer inputs */ export class DeviceValidators { + static readonly STRING_FORMAT_MAX_LENGTH = 28; readonly STRING_FORMAT_REGEXP = new RegExp( - "^([a-z0-9\\p{L}\\p{M}.',-_ ]{1,28})$", + "^([a-z0-9\\p{L}\\p{M}.',-_ ]{1," + + DeviceValidators.STRING_FORMAT_MAX_LENGTH + + '})$', 'u' ); diff --git a/modules/ui/src/app/pages/devices/devices.component.html b/modules/ui/src/app/pages/devices/devices.component.html index c9f5d3aee..aef3730c5 100644 --- a/modules/ui/src/app/pages/devices/devices.component.html +++ b/modules/ui/src/app/pages/devices/devices.component.html @@ -22,8 +22,10 @@

Devices

Devices + +
{ name.dispatchEvent(new Event('input')); component.nameControl.markAsTouched(); - fixture.detectChanges(); fixture.detectChanges(); const nameError = compiled.querySelector('mat-error')?.innerHTML; @@ -388,9 +389,52 @@ describe('ProfileFormComponent', () => { }); }); }); + + describe('Discard button', () => { + beforeEach(() => { + fillForm(component); + fixture.detectChanges(); + }); + + it('should be enabled when form is filled', () => { + const discardButton = compiled.querySelector( + '.discard-button' + ) as HTMLButtonElement; + + expect(discardButton.disabled).toBeFalse(); + }); + + it('should emit discard', () => { + const emitSpy = spyOn(component.discard, 'emit'); + const discardButton = compiled.querySelector( + '.discard-button' + ) as HTMLButtonElement; + discardButton.click(); + + expect(emitSpy).toHaveBeenCalled(); + }); + }); }); describe('Class tests', () => { + describe('with outdated draft profile', () => { + beforeEach(() => { + component.selectedProfile = OUTDATED_DRAFT_PROFILE_MOCK; + fixture.detectChanges(); + }); + + it('should have an error when uses the name of copy profile', () => { + expect(component.profileForm.value).toEqual({ + 0: '', + 1: 'IoT Sensor', + 2: '', + 3: { 0: false, 1: false, 2: false }, + 4: '', + name: 'Outdated profile', + }); + }); + }); + describe('with profile', () => { beforeEach(() => { component.selectedProfile = PROFILE_MOCK; @@ -432,6 +476,15 @@ describe('ProfileFormComponent', () => { component.nameControl.hasError('has_same_profile_name') ).toBeTrue(); }); + + it('should have an error when uses the name of copy profile', () => { + component.selectedProfile = COPY_PROFILE_MOCK; + component.profiles = [PROFILE_MOCK, PROFILE_MOCK_2, COPY_PROFILE_MOCK]; + + expect( + component.nameControl.hasError('has_same_profile_name') + ).toBeTrue(); + }); }); describe('with no profile', () => { diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts index a15867ae7..567eb6c34 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts @@ -105,6 +105,7 @@ export class ProfileFormComponent implements OnInit { } @Output() saveProfile = new EventEmitter(); + @Output() discard = new EventEmitter(); constructor( private deviceValidators: DeviceValidators, private profileValidators: ProfileValidators, @@ -206,18 +207,22 @@ export class ProfileFormComponent implements OnInit { fillProfileForm(profileFormat: ProfileFormat[], profile: Profile): void { this.nameControl.setValue(profile.name); profileFormat.forEach((question, index) => { + const answer = profile.questions.find( + answers => answers.question === question.question + ); if (question.type === FormControlType.SELECT_MULTIPLE) { question.options?.forEach((item, idx) => { - if ((profile.questions[index].answer as number[])?.includes(idx)) { + if ((answer?.answer as number[])?.includes(idx)) { this.getFormGroup(index).controls[idx].setValue(true); } else { this.getFormGroup(index).controls[idx].setValue(false); } }); } else { - this.getControl(index).setValue(profile.questions[index].answer); + this.getControl(index).setValue(answer?.answer || ''); } }); + this.nameControl.markAsTouched(); this.triggerResize(); } @@ -241,6 +246,10 @@ export class ProfileFormComponent implements OnInit { } } + onDiscardClick() { + this.discard.emit(); + } + private buildResponseFromForm( initialQuestions: ProfileFormat[], profileForm: FormGroup, diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts b/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts index dcad4b397..34bac3ebf 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts @@ -37,7 +37,13 @@ export class ProfileValidators { ): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { const value = control.value?.trim(); - if (value && profiles.length && (!profile || profile?.name !== value)) { + if ( + value && + profiles.length && + (!profile || + !profile.created || + (profile.created && profile?.name !== value)) + ) { const isSameProfileName = this.hasSameProfileName(value, profiles); return isSameProfileName ? { has_same_profile_name: true } : null; } diff --git a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.html b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.html index 35850f0ed..31049cd93 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.html +++ b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.html @@ -13,17 +13,30 @@ See the License for the specific language governing permissions and limitations under the License. --> -
+
+ (keydown.enter)="enterProfileItem(profile)"> + [attr.aria-label]=" + profile.status === ProfileStatus.EXPIRED + ? EXPIRED_TOOLTIP + : profile.status + "> + +

- {{ profile.created | date: 'dd MMM yyyy' }} + + Outdated ({{ profile.created | date: 'dd MMM yyyy' }}) + + + {{ profile.created | date: 'dd MMM yyyy' }} +

+ diff --git a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.scss b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.scss index a9a22b9e4..739a7bd14 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.scss +++ b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.scss @@ -28,15 +28,28 @@ $profile-item-container-gap: 16px; .profile-item-container { display: grid; - grid-template-columns: minmax(160px, 1fr) $profile-icon-container-size; + grid-template-columns: minmax(160px, 1fr) repeat( + 2, + $profile-icon-container-size + ); gap: $profile-item-container-gap; box-sizing: border-box; padding: 12px 16px; border-bottom: 1px solid $lighter-grey; align-items: center; - height: 92px; + min-height: 92px; + &-expired { + grid-template-columns: minmax(160px, 1fr) $profile-icon-container-size; + } } +.profile-item-container-expired .profile-item-info { + .profile-item-icon, + .profile-item-name, + .profile-item-created { + color: $red-800; + } +} .profile-item-icon-container { grid-area: icon; display: inline-block; diff --git a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.spec.ts b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.spec.ts index ae48e64ec..56f9ad6a4 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.spec.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.spec.ts @@ -16,8 +16,12 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ProfileItemComponent } from './profile-item.component'; -import { PROFILE_MOCK } from '../../../mocks/profile.mock'; +import { + EXPIRED_PROFILE_MOCK, + PROFILE_MOCK, +} from '../../../mocks/profile.mock'; import { TestRunService } from '../../../services/test-run.service'; +import { LiveAnnouncer } from '@angular/cdk/a11y'; describe('ProfileItemComponent', () => { let component: ProfileItemComponent; @@ -25,11 +29,16 @@ describe('ProfileItemComponent', () => { let compiled: HTMLElement; const testRunServiceMock = jasmine.createSpyObj(['getRiskClass']); - + const mockLiveAnnouncer = jasmine.createSpyObj('mockLiveAnnouncer', [ + 'announce', + ]); beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ProfileItemComponent], - providers: [{ provide: TestRunService, useValue: testRunServiceMock }], + providers: [ + { provide: TestRunService, useValue: testRunServiceMock }, + { provide: LiveAnnouncer, useValue: mockLiveAnnouncer }, + ], }).compileComponents(); fixture = TestBed.createComponent(ProfileItemComponent); @@ -59,8 +68,12 @@ describe('ProfileItemComponent', () => { const deleteButton = fixture.nativeElement.querySelector( '.profile-item-button.delete' ); + const copyButton = fixture.nativeElement.querySelector( + '.profile-item-button.copy' + ); expect(deleteButton?.ariaLabel?.trim()).toContain(PROFILE_MOCK.name); + expect(copyButton?.ariaLabel?.trim()).toContain(PROFILE_MOCK.name); }); it('should emit delete event on delete button clicked', () => { @@ -84,4 +97,22 @@ describe('ProfileItemComponent', () => { expect(profileClickedSpy).toHaveBeenCalledWith(PROFILE_MOCK); }); + + describe('with Expired profile', () => { + beforeEach(() => { + component.enterProfileItem(EXPIRED_PROFILE_MOCK); + }); + + it('should change tooltip on enterProfileItem', () => { + expect(component.tooltip.message).toEqual( + 'This risk profile is outdated. Please create a new risk profile.' + ); + }); + + it('should announce', () => { + expect(mockLiveAnnouncer.announce).toHaveBeenCalledWith( + 'This risk profile is outdated. Please create a new risk profile.' + ); + }); + }); }); diff --git a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.ts b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.ts index 79bd08833..514cbd46e 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.ts @@ -17,8 +17,10 @@ import { ChangeDetectionStrategy, Component, EventEmitter, + HostListener, Input, Output, + ViewChild, } from '@angular/core'; import { Profile, @@ -29,25 +31,55 @@ import { MatIcon } from '@angular/material/icon'; import { MatButtonModule } from '@angular/material/button'; import { CommonModule } from '@angular/common'; import { TestRunService } from '../../../services/test-run.service'; -import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip'; +import { LiveAnnouncer } from '@angular/cdk/a11y'; @Component({ selector: 'app-profile-item', standalone: true, imports: [MatIcon, MatButtonModule, CommonModule, MatTooltipModule], + providers: [MatTooltip], templateUrl: './profile-item.component.html', styleUrl: './profile-item.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) export class ProfileItemComponent { public readonly ProfileStatus = ProfileStatus; + public readonly EXPIRED_TOOLTIP = + 'Expired. Please, create a new Risk profile.'; @Input() profile!: Profile; @Output() deleteButtonClicked = new EventEmitter(); @Output() profileClicked = new EventEmitter(); + @Output() copyProfileClicked = new EventEmitter(); - constructor(private readonly testRunService: TestRunService) {} + @ViewChild('tooltip') tooltip!: MatTooltip; + + @HostListener('focusout', ['$event']) + outEvent(): void { + if (this.profile.status === ProfileStatus.EXPIRED) { + this.tooltip.message = this.EXPIRED_TOOLTIP; + } + } + + constructor( + private readonly testRunService: TestRunService, + private liveAnnouncer: LiveAnnouncer + ) {} public getRiskClass(riskResult: string): RiskResultClassName { return this.testRunService.getRiskClass(riskResult); } + + public async enterProfileItem(profile: Profile) { + if (profile.status === ProfileStatus.EXPIRED) { + this.tooltip.message = + 'This risk profile is outdated. Please create a new risk profile.'; + this.tooltip.show(); + await this.liveAnnouncer.announce( + 'This risk profile is outdated. Please create a new risk profile.' + ); + } else { + this.profileClicked.emit(profile); + } + } } diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html index 2f11ea76b..c5e38e360 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html @@ -25,9 +25,8 @@

Risk assessment

[selectedProfile]="vm.selectedProfile" [profiles]="vm.profiles" [profileFormat]="vm.profileFormat" - (saveProfile)=" - saveProfileClicked($event, vm.selectedProfile) - "> + (saveProfile)="saveProfileClicked($event, vm.selectedProfile)" + (discard)="discard(vm.selectedProfile)">
@@ -43,16 +42,13 @@

Saved profiles

+ (profileClicked)="profileClicked($event)" + (copyProfileClicked)="copyProfileAndOpenForm($event)">
diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss index c4ef49782..c7241c6c4 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss @@ -61,7 +61,7 @@ .main-content { padding: 16px 32px; - overflow: scroll; + overflow: hidden; width: calc(100% - $profiles-drawer-width); } diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts index e2aa6332e..8e792ff83 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts @@ -26,7 +26,12 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { TestRunService } from '../../services/test-run.service'; import SpyObj = jasmine.SpyObj; import { MatSidenavModule } from '@angular/material/sidenav'; -import { NEW_PROFILE_MOCK, PROFILE_MOCK } from '../../mocks/profile.mock'; +import { + COPY_PROFILE_MOCK, + NEW_PROFILE_MOCK, + NEW_PROFILE_MOCK_DRAFT, + PROFILE_MOCK, +} from '../../mocks/profile.mock'; import { of } from 'rxjs'; import { Component, Input } from '@angular/core'; import { Profile, ProfileFormat } from '../../model/profile'; @@ -217,6 +222,13 @@ describe('RiskAssessmentComponent', () => { }); }); + describe('#getCopyOfProfile', () => { + it('should open the form with copy of profile', () => { + const copy = component.getCopyOfProfile(PROFILE_MOCK); + expect(copy).toEqual(COPY_PROFILE_MOCK); + }); + }); + describe('#saveProfile', () => { describe('with no profile selected', () => { beforeEach(() => { @@ -236,7 +248,7 @@ describe('RiskAssessmentComponent', () => { }); describe('with profile selected', () => { - it('should open save profile modal', fakeAsync(() => { + it('should open save profile modal for valid profile', fakeAsync(() => { const openSpy = spyOn(component.dialog, 'open').and.returnValue({ afterClosed: () => of(true), } as MatDialogRef); @@ -244,9 +256,31 @@ describe('RiskAssessmentComponent', () => { component.saveProfileClicked(NEW_PROFILE_MOCK, PROFILE_MOCK); expect(openSpy).toHaveBeenCalledWith(SimpleDialogComponent, { - ariaLabel: 'Save changes', + ariaLabel: 'Save profile', data: { - title: 'Save changes', + title: 'Save profile', + content: `You are about to save changes in Primary profile. Are you sure?`, + }, + autoFocus: true, + hasBackdrop: true, + disableClose: true, + panelClass: 'simple-dialog', + }); + + openSpy.calls.reset(); + })); + + it('should open save draft profile modal', fakeAsync(() => { + const openSpy = spyOn(component.dialog, 'open').and.returnValue({ + afterClosed: () => of(true), + } as MatDialogRef); + + component.saveProfileClicked(NEW_PROFILE_MOCK_DRAFT, PROFILE_MOCK); + + expect(openSpy).toHaveBeenCalledWith(SimpleDialogComponent, { + ariaLabel: 'Save draft profile', + data: { + title: 'Save draft profile', content: `You are about to save changes in Primary profile. Are you sure?`, }, autoFocus: true, @@ -284,6 +318,42 @@ describe('RiskAssessmentComponent', () => { })); }); }); + + describe('#discard', () => { + describe('with no selected profile', () => { + beforeEach(() => { + component.discard(null); + }); + + it('should call setFocusOnCreateButton', () => { + expect( + mockRiskAssessmentStore.setFocusOnCreateButton + ).toHaveBeenCalled(); + }); + + it('should close the form', () => { + expect(component.isOpenProfileForm).toBeFalse(); + }); + }); + + describe('with selected profile', () => { + beforeEach(() => { + component.discard(PROFILE_MOCK); + }); + + it('should call setFocusOnCreateButton', () => { + expect( + mockRiskAssessmentStore.setFocusOnSelectedProfile + ).toHaveBeenCalled(); + }); + + it('should update selected profile', () => { + expect( + mockRiskAssessmentStore.updateSelectedProfile + ).toHaveBeenCalledWith(null); + }); + }); + }); }); }); diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts index 503d87a52..dd3d33d9d 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts @@ -24,8 +24,9 @@ import { SimpleDialogComponent } from '../../components/simple-dialog/simple-dia import { Subject, takeUntil } from 'rxjs'; import { MatDialog } from '@angular/material/dialog'; import { LiveAnnouncer } from '@angular/cdk/a11y'; -import { Profile } from '../../model/profile'; +import { Profile, ProfileStatus } from '../../model/profile'; import { Observable } from 'rxjs/internal/Observable'; +import { DeviceValidators } from '../devices/components/device-form/device.validators'; @Component({ selector: 'app-risk-assessment', @@ -53,6 +54,12 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { this.destroy$.unsubscribe(); } + async profileClicked(profile: Profile | null = null) { + if (profile === null || profile.status !== ProfileStatus.EXPIRED) { + await this.openForm(profile); + } + } + async openForm(profile: Profile | null = null) { this.isOpenProfileForm = true; this.store.updateSelectedProfile(profile); @@ -60,6 +67,27 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { this.store.setFocusOnProfileForm(); } + async copyProfileAndOpenForm(profile: Profile) { + await this.openForm(this.getCopyOfProfile(profile)); + } + + getCopyOfProfile(profile: Profile): Profile { + const copyOfProfile = { ...profile }; + copyOfProfile.name = this.getCopiedProfileName(profile.name); + delete copyOfProfile.created; // new profile is not create yet + return copyOfProfile; + } + + private getCopiedProfileName(name: string): string { + name = `Copy of ${name}`; + if (name.length > DeviceValidators.STRING_FORMAT_MAX_LENGTH) { + name = + name.substring(0, DeviceValidators.STRING_FORMAT_MAX_LENGTH - 3) + + '...'; + } + return name; + } + deleteProfile( profileName: string, index: number, @@ -94,7 +122,10 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { this.saveProfile(profile); this.store.setFocusOnCreateButton(); } else { - this.openSaveDialog(selectedProfile.name) + this.openSaveDialog( + selectedProfile.name, + profile.status === ProfileStatus.DRAFT + ) .pipe(takeUntil(this.destroy$)) .subscribe(saveProfile => { if (saveProfile) { @@ -105,8 +136,18 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { } } - trackByIndex = (index: number): number => { - return index; + discard(selectedProfile: Profile | null) { + this.isOpenProfileForm = false; + if (selectedProfile) { + this.store.setFocusOnSelectedProfile(); + this.store.updateSelectedProfile(null); + } else { + this.store.setFocusOnCreateButton(); + } + } + + trackByName = (index: number, item: Profile): string => { + return item.name; }; private closeFormAfterDelete(name: string, selectedProfile: Profile | null) { @@ -132,11 +173,14 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { this.store.setFocus({ nextItem, firstItem }); } - private openSaveDialog(profileName: string): Observable { + private openSaveDialog( + profileName: string, + draft: boolean = false + ): Observable { const dialogRef = this.dialog.open(SimpleDialogComponent, { - ariaLabel: 'Save changes', + ariaLabel: `Save ${draft ? 'draft profile' : 'profile'}`, data: { - title: 'Save changes', + title: `Save ${draft ? 'draft profile' : 'profile'}`, content: `You are about to save changes in ${profileName}. Are you sure?`, }, autoFocus: true, diff --git a/modules/ui/src/app/pages/settings/settings.component.html b/modules/ui/src/app/pages/settings/settings.component.html index 36849b42e..089ebd5eb 100644 --- a/modules/ui/src/app/pages/settings/settings.component.html +++ b/modules/ui/src/app/pages/settings/settings.component.html @@ -116,7 +116,7 @@

System settings

- Warning! No ports is detected. + Warning! No ports detected.
diff --git a/modules/ui/src/app/pages/settings/settings.store.spec.ts b/modules/ui/src/app/pages/settings/settings.store.spec.ts index 669faef98..b51e1f2a6 100644 --- a/modules/ui/src/app/pages/settings/settings.store.spec.ts +++ b/modules/ui/src/app/pages/settings/settings.store.spec.ts @@ -25,13 +25,17 @@ import { TestBed } from '@angular/core/testing'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { AppState } from '../../store/state'; import { skip, take } from 'rxjs'; -import { selectHasConnectionSettings } from '../../store/selectors'; +import { + selectAdapters, + selectHasConnectionSettings, +} from '../../store/selectors'; import { of } from 'rxjs/internal/observable/of'; import { fetchSystemConfigSuccess } from '../../store/actions'; import { fetchInterfacesSuccess } from '../../store/actions'; import { FormBuilder, FormControl } from '@angular/forms'; import { FormKey, SystemConfig } from '../../model/setting'; import { + MOCK_ADAPTERS, MOCK_DEVICE_VALUE, MOCK_INTERFACE_VALUE, MOCK_INTERFACES, @@ -60,7 +64,10 @@ describe('SettingsStore', () => { SettingsStore, { provide: TestRunService, useValue: mockService }, provideMockStore({ - selectors: [{ selector: selectHasConnectionSettings, value: true }], + selectors: [ + { selector: selectHasConnectionSettings, value: true }, + { selector: selectAdapters, value: {} }, + ], }), FormBuilder, ], @@ -308,5 +315,39 @@ describe('SettingsStore', () => { }); }); }); + + describe('adaptersUpdate', () => { + const updateInterfaces = { + mockDeviceKey: 'mockDeviceValue', + mockNewInternetKey: 'mockNewInternetValue', + }; + const updateInternetOptions = { + '': 'Not specified', + mockDeviceKey: 'mockDeviceValue', + mockNewInternetKey: 'mockNewInternetValue', + }; + + beforeEach(() => { + settingsStore.setInterfaces(MOCK_INTERFACES); + }); + + it('should update store', done => { + settingsStore.viewModel$ + .pipe(skip(3), take(1)) + .subscribe(storeValue => { + expect(storeValue.interfaces).toEqual(updateInterfaces); + expect(storeValue.deviceOptions).toEqual(updateInterfaces); + expect(storeValue.internetOptions).toEqual(updateInternetOptions); + + expect(store.dispatch).toHaveBeenCalledWith( + fetchInterfacesSuccess({ interfaces: updateInterfaces }) + ); + done(); + }); + + store.overrideSelector(selectAdapters, MOCK_ADAPTERS); + store.refreshState(); + }); + }); }); }); diff --git a/modules/ui/src/app/pages/settings/settings.store.ts b/modules/ui/src/app/pages/settings/settings.store.ts index f489228a9..fc4a00dc9 100644 --- a/modules/ui/src/app/pages/settings/settings.store.ts +++ b/modules/ui/src/app/pages/settings/settings.store.ts @@ -23,12 +23,15 @@ import { SystemConfig, SystemInterfaces, } from '../../model/setting'; -import { exhaustMap, switchMap, Observable } from 'rxjs'; +import { exhaustMap, switchMap, Observable, skip } from 'rxjs'; import { tap, withLatestFrom } from 'rxjs/operators'; import * as AppActions from '../../store/actions'; import { Store } from '@ngrx/store'; import { AppState } from '../../store/state'; -import { selectHasConnectionSettings } from '../../store/selectors'; +import { + selectAdapters, + selectHasConnectionSettings, +} from '../../store/selectors'; import { FormControl, FormGroup } from '@angular/forms'; export interface SettingsComponentState { @@ -75,6 +78,8 @@ export class SettingsStore extends ComponentStore { private hasConnectionSettings$ = this.store.select( selectHasConnectionSettings ); + + private adapters$ = this.store.select(selectAdapters); private isSubmitting$ = this.select(state => state.isSubmitting); private isLessThanOneInterfaces$ = this.select( state => state.isLessThanOneInterface @@ -108,26 +113,25 @@ export class SettingsStore extends ComponentStore { isSubmitting, })); - setInterfaces = this.updater((state, interfaces: SystemInterfaces) => ({ - ...state, - interfaces, - deviceOptions: interfaces, - internetOptions: { - ...DEFAULT_INTERNET_OPTION, - ...interfaces, - }, - isLessThanOneInterface: Object.keys(interfaces).length < 1, - })); + setInterfaces = this.updater((state, interfaces: SystemInterfaces) => { + return { + ...state, + interfaces, + deviceOptions: interfaces, + internetOptions: { + ...DEFAULT_INTERNET_OPTION, + ...interfaces, + }, + isLessThanOneInterface: Object.keys(interfaces).length < 1, + }; + }); getInterfaces = this.effect(trigger$ => { return trigger$.pipe( exhaustMap(() => { return this.testRunService.getSystemInterfaces().pipe( tap((interfaces: SystemInterfaces) => { - this.store.dispatch( - AppActions.fetchInterfacesSuccess({ interfaces }) - ); - this.setInterfaces(interfaces); + this.updateInterfaces(interfaces); }) ); }) @@ -202,6 +206,48 @@ export class SettingsStore extends ComponentStore { ); }); + adaptersUpdate = this.effect(() => { + return this.adapters$.pipe( + skip(1), + withLatestFrom(this.interfaces$), + tap(([adapters, interfaces]) => { + const updatedInterfaces = { ...interfaces }; + if (adapters.adapters_added) { + this.addInterfaces(adapters.adapters_added, updatedInterfaces); + } + if (adapters.adapters_removed) { + this.removeInterfaces(adapters.adapters_removed, updatedInterfaces); + } + this.updateInterfaces(updatedInterfaces); + }) + ); + }); + + private updateInterfaces(interfaces: SystemInterfaces) { + this.store.dispatch( + AppActions.fetchInterfacesSuccess({ interfaces: interfaces }) + ); + this.setInterfaces(interfaces); + } + + private addInterfaces( + newInterfaces: SystemInterfaces, + interfaces: SystemInterfaces + ): void { + for (const [key, value] of Object.entries(newInterfaces)) { + interfaces[key] = value; + } + } + + private removeInterfaces( + interfacesToDelete: SystemInterfaces, + interfaces: SystemInterfaces + ): void { + for (const key of Object.keys(interfacesToDelete)) { + delete interfaces[key]; + } + } + private setDefaultDeviceInterfaceValue( value: string | undefined, options: { [key: string]: string }, diff --git a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.spec.ts b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.spec.ts index dd614c9d1..2a99f17b0 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.spec.ts +++ b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.spec.ts @@ -29,7 +29,7 @@ import { ReactiveFormsModule } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { DeviceTestsComponent } from '../../../../components/device-tests/device-tests.component'; -import { device } from '../../../../mocks/device.mock'; +import { device, MOCK_TEST_MODULES } from '../../../../mocks/device.mock'; import { of } from 'rxjs'; import { MOCK_PROGRESS_DATA_WAITING_FOR_DEVICE } from '../../../../mocks/testrun.mock'; import { SpinnerComponent } from '../../../../components/spinner/spinner.component'; @@ -44,25 +44,12 @@ describe('ProgressInitiateFormComponent', () => { const testRunServiceMock = jasmine.createSpyObj([ 'getDevices', 'fetchDevices', - 'getTestModules', 'startTestrun', 'systemStatus$', 'getSystemStatus', 'fetchVersion', 'setIsOpenStartTestrun', ]); - testRunServiceMock.getTestModules.and.returnValue([ - { - displayName: 'Connection', - name: 'connection', - enabled: true, - }, - { - displayName: 'DNS', - name: 'dns', - enabled: false, - }, - ]); testRunServiceMock.getDevices.and.returnValue( new BehaviorSubject([device, device]) ); @@ -81,7 +68,10 @@ describe('ProgressInitiateFormComponent', () => { close: () => ({}), }, }, - { provide: MAT_DIALOG_DATA, useValue: {} }, + { + provide: MAT_DIALOG_DATA, + useValue: { testModules: MOCK_TEST_MODULES }, + }, provideMockStore({ selectors: [{ selector: selectDevices, value: [device, device] }], }), @@ -214,7 +204,7 @@ describe('ProgressInitiateFormComponent', () => { connection: { enabled: true, }, - dns: { + udmi: { enabled: true, }, }, diff --git a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts index c1a2afb92..a526e0973 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts +++ b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts @@ -42,6 +42,7 @@ import { TestrunStatus } from '../../../../model/testrun-status'; interface DialogData { device?: Device; + testModules: TestModule[]; } @Component({ @@ -91,7 +92,7 @@ export class TestrunInitiateFormComponent ngOnInit() { this.createInitiateForm(); - this.testModules = this.testRunService.getTestModules(); + this.testModules = this.data?.testModules; if (this.data?.device) { this.deviceSelected(this.data.device); diff --git a/modules/ui/src/app/pages/testrun/testrun.component.html b/modules/ui/src/app/pages/testrun/testrun.component.html index d485d1926..74b414abf 100644 --- a/modules/ui/src/app/pages/testrun/testrun.component.html +++ b/modules/ui/src/app/pages/testrun/testrun.component.html @@ -99,7 +99,7 @@

isTestrunInProgress(systemStatus?.status) || systemStatus?.status === StatusOfTestrun.Cancelling " - (click)="openTestRunModal()" + (click)="openTestRunModal(vm.testModules)" mat-flat-button> Start New Testrun diff --git a/modules/ui/src/app/pages/testrun/testrun.component.spec.ts b/modules/ui/src/app/pages/testrun/testrun.component.spec.ts index 98d1b986f..5867bc313 100644 --- a/modules/ui/src/app/pages/testrun/testrun.component.spec.ts +++ b/modules/ui/src/app/pages/testrun/testrun.component.spec.ts @@ -53,6 +53,7 @@ import { selectIsOpenWaitSnackBar, selectRiskProfiles, selectSystemStatus, + selectTestModules, } from '../../store/selectors'; import { TestrunStore } from './testrun.store'; import { @@ -123,6 +124,7 @@ describe('TestrunComponent', () => { { selector: selectIsOpenWaitSnackBar, value: false }, { selector: selectHasRiskProfiles, value: false }, { selector: selectRiskProfiles, value: [] }, + { selector: selectTestModules, value: [] }, { selector: selectSystemStatus, value: MOCK_PROGRESS_DATA_IN_PROGRESS, @@ -234,9 +236,17 @@ describe('TestrunComponent', () => { }, provideMockStore({ selectors: [ - { selector: selectHasDevices, value: false }, { selector: selectDevices, value: [] }, + { selector: selectHasDevices, value: false }, + { selector: selectIsOpenStartTestrun, value: false }, { selector: selectIsOpenWaitSnackBar, value: false }, + { selector: selectHasRiskProfiles, value: false }, + { selector: selectRiskProfiles, value: [] }, + { selector: selectTestModules, value: [] }, + { + selector: selectSystemStatus, + value: MOCK_PROGRESS_DATA_IN_PROGRESS, + }, ], }), ], @@ -324,6 +334,9 @@ describe('TestrunComponent', () => { hasBackdrop: true, disableClose: true, panelClass: 'initiate-test-run-dialog', + data: { + testModules: [], + }, }); expect(store.dispatch).toHaveBeenCalledWith( fetchSystemStatusSuccess({ @@ -405,6 +418,7 @@ describe('TestrunComponent', () => { MOCK_PROGRESS_DATA_COMPLIANT ); store.overrideSelector(selectHasDevices, true); + store.refreshState(); fixture.detectChanges(); }); diff --git a/modules/ui/src/app/pages/testrun/testrun.component.ts b/modules/ui/src/app/pages/testrun/testrun.component.ts index b861ef953..ce5c104d5 100644 --- a/modules/ui/src/app/pages/testrun/testrun.component.ts +++ b/modules/ui/src/app/pages/testrun/testrun.component.ts @@ -34,6 +34,8 @@ import { FocusManagerService } from '../../services/focus-manager.service'; import { TestrunStore } from './testrun.store'; import { TestRunService } from '../../services/test-run.service'; import { NotificationService } from '../../services/notification.service'; +import { TestModule } from '../../model/device'; +import { combineLatest } from 'rxjs/internal/observable/combineLatest'; @Component({ selector: 'app-progress', @@ -60,11 +62,14 @@ export class TestrunComponent implements OnInit, OnDestroy { ) {} ngOnInit(): void { - this.testrunStore.isOpenStartTestrun$ + combineLatest([ + this.testrunStore.isOpenStartTestrun$, + this.testrunStore.testModules$, + ]) .pipe(takeUntil(this.destroy$)) - .subscribe(isOpenStartTestrun => { + .subscribe(([isOpenStartTestrun, testModules]) => { if (isOpenStartTestrun) { - this.openTestRunModal(); + this.openTestRunModal(testModules); } }); } @@ -126,13 +131,16 @@ export class TestrunComponent implements OnInit, OnDestroy { this.destroy$.unsubscribe(); } - openTestRunModal(): void { + openTestRunModal(testModules: TestModule[]): void { const dialogRef = this.dialog.open(TestrunInitiateFormComponent, { ariaLabel: 'Initiate testrun', autoFocus: true, hasBackdrop: true, disableClose: true, panelClass: 'initiate-test-run-dialog', + data: { + testModules, + }, }); dialogRef diff --git a/modules/ui/src/app/pages/testrun/testrun.store.spec.ts b/modules/ui/src/app/pages/testrun/testrun.store.spec.ts index 03f7817af..e8be0f93d 100644 --- a/modules/ui/src/app/pages/testrun/testrun.store.spec.ts +++ b/modules/ui/src/app/pages/testrun/testrun.store.spec.ts @@ -23,6 +23,7 @@ import { selectIsOpenStartTestrun, selectRiskProfiles, selectSystemStatus, + selectTestModules, } from '../../store/selectors'; import { fetchSystemStatus, @@ -66,6 +67,7 @@ describe('TestrunStore', () => { { selector: selectHasConnectionSettings, value: true }, { selector: selectIsOpenStartTestrun, value: false }, { selector: selectRiskProfiles, value: [] }, + { selector: selectTestModules, value: [] }, ], }), ], @@ -89,6 +91,7 @@ describe('TestrunStore', () => { dataSource: [], stepsToResolveCount: 0, profiles: [], + testModules: [], }); done(); }); diff --git a/modules/ui/src/app/pages/testrun/testrun.store.ts b/modules/ui/src/app/pages/testrun/testrun.store.ts index eacad9959..4f4edabd2 100644 --- a/modules/ui/src/app/pages/testrun/testrun.store.ts +++ b/modules/ui/src/app/pages/testrun/testrun.store.ts @@ -24,6 +24,7 @@ import { selectIsOpenStartTestrun, selectRiskProfiles, selectSystemStatus, + selectTestModules, } from '../../store/selectors'; import { fetchSystemStatus, @@ -41,12 +42,14 @@ import { } from '../../model/testrun-status'; import { FocusManagerService } from '../../services/focus-manager.service'; import { LoaderService } from '../../services/loader.service'; +import { TestModule } from '../../model/device'; const EMPTY_RESULT = new Array(100).fill(null).map(() => ({}) as IResult); export interface TestrunComponentState { dataSource: IResult[] | undefined; stepsToResolveCount: number; + testModules: TestModule[]; } @Injectable() @@ -59,12 +62,15 @@ export class TestrunStore extends ComponentStore { private profiles$ = this.store.select(selectRiskProfiles); private systemStatus$ = this.store.select(selectSystemStatus); isOpenStartTestrun$ = this.store.select(selectIsOpenStartTestrun); + testModules$ = this.store.select(selectTestModules); + viewModel$ = this.select({ hasDevices: this.hasDevices$, systemStatus: this.systemStatus$, dataSource: this.dataSource$, stepsToResolveCount: this.stepsToResolveCount$, profiles: this.profiles$, + testModules: this.testModules$, }); setDataSource = this.updater((state, dataSource: IResult[] | undefined) => { @@ -215,6 +221,7 @@ export class TestrunStore extends ComponentStore { super({ dataSource: undefined, stepsToResolveCount: 0, + testModules: [], }); } } diff --git a/modules/ui/src/app/services/test-run-mqtt.service.spec.ts b/modules/ui/src/app/services/test-run-mqtt.service.spec.ts new file mode 100644 index 000000000..19bda437a --- /dev/null +++ b/modules/ui/src/app/services/test-run-mqtt.service.spec.ts @@ -0,0 +1,102 @@ +import { TestBed } from '@angular/core/testing'; + +import { TestRunMqttService } from './test-run-mqtt.service'; +import { IMqttMessage, MqttModule, MqttService } from 'ngx-mqtt'; +import { MQTT_SERVICE_OPTIONS } from '../app.module'; +import SpyObj = jasmine.SpyObj; +import { of } from 'rxjs'; +import { MOCK_ADAPTERS } from '../mocks/settings.mock'; +import { Topic } from '../model/topic'; +import { MOCK_INTERNET } from '../mocks/topic.mock'; +import { MOCK_PROGRESS_DATA_IN_PROGRESS } from '../mocks/testrun.mock'; + +describe('TestRunMqttService', () => { + let service: TestRunMqttService; + let mockService: SpyObj; + + beforeEach(() => { + mockService = jasmine.createSpyObj(['observe']); + + TestBed.configureTestingModule({ + imports: [MqttModule.forRoot(MQTT_SERVICE_OPTIONS)], + providers: [{ provide: MqttService, useValue: mockService }], + }); + service = TestBed.inject(TestRunMqttService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + describe('getNetworkAdapters', () => { + beforeEach(() => { + mockService.observe.and.returnValue(of(getResponse(MOCK_ADAPTERS))); + }); + + it('should subscribe the topic', done => { + service.getNetworkAdapters().subscribe(() => { + expect(mockService.observe).toHaveBeenCalledWith(Topic.NetworkAdapters); + done(); + }); + }); + + it('should return object of type', done => { + service.getNetworkAdapters().subscribe(res => { + expect(res).toEqual(MOCK_ADAPTERS); + done(); + }); + }); + }); + + describe('getInternetConnection', () => { + beforeEach(() => { + mockService.observe.and.returnValue(of(getResponse(MOCK_INTERNET))); + }); + + it('should subscribe the topic', done => { + service.getInternetConnection().subscribe(() => { + expect(mockService.observe).toHaveBeenCalledWith( + Topic.InternetConnection + ); + done(); + }); + }); + + it('should return object of type', done => { + service.getInternetConnection().subscribe(res => { + expect(res).toEqual(MOCK_INTERNET); + done(); + }); + }); + }); + + describe('getStatus', () => { + beforeEach(() => { + mockService.observe.and.returnValue( + of(getResponse(MOCK_PROGRESS_DATA_IN_PROGRESS)) + ); + }); + + it('should subscribe the topic', done => { + service.getStatus().subscribe(() => { + expect(mockService.observe).toHaveBeenCalledWith(Topic.Status); + done(); + }); + }); + + it('should return object of type', done => { + service.getStatus().subscribe(res => { + expect(res).toEqual(MOCK_PROGRESS_DATA_IN_PROGRESS); + done(); + }); + }); + }); + + function getResponse(response: Type): IMqttMessage { + const enc = new TextEncoder(); + const message = enc.encode(JSON.stringify(response)); + return { + payload: message, + } as IMqttMessage; + } +}); diff --git a/modules/ui/src/app/services/test-run-mqtt.service.ts b/modules/ui/src/app/services/test-run-mqtt.service.ts new file mode 100644 index 000000000..d5e805da6 --- /dev/null +++ b/modules/ui/src/app/services/test-run-mqtt.service.ts @@ -0,0 +1,38 @@ +import { Injectable } from '@angular/core'; +import { IMqttMessage, MqttService } from 'ngx-mqtt'; +import { catchError, Observable, of } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Adapters } from '../model/setting'; +import { TestrunStatus } from '../model/testrun-status'; +import { InternetConnection, Topic } from '../model/topic'; + +@Injectable({ + providedIn: 'root', +}) +export class TestRunMqttService { + constructor(private mqttService: MqttService) {} + + getNetworkAdapters(): Observable { + return this.topic(Topic.NetworkAdapters); + } + + getInternetConnection(): Observable { + return this.topic(Topic.InternetConnection); + } + + getStatus(): Observable { + return this.topic(Topic.Status); + } + + private topic(topicName: string): Observable { + return this.mqttService.observe(topicName).pipe( + map( + (res: IMqttMessage) => + JSON.parse(new TextDecoder().decode(res.payload)) as Type + ), + catchError(() => { + return of({} as Type); + }) + ); + } +} diff --git a/modules/ui/src/app/services/test-run.service.spec.ts b/modules/ui/src/app/services/test-run.service.spec.ts index 069c94c0a..c3c40185e 100644 --- a/modules/ui/src/app/services/test-run.service.spec.ts +++ b/modules/ui/src/app/services/test-run.service.spec.ts @@ -18,7 +18,7 @@ import { HttpTestingController, } from '@angular/common/http/testing'; import { fakeAsync, getTestBed, TestBed, tick } from '@angular/core/testing'; -import { Device, TestModule } from '../model/device'; +import { Device } from '../model/device'; import { TestRunService, UNAVAILABLE_VERSION } from './test-run.service'; import { SystemConfig, SystemInterfaces } from '../model/setting'; @@ -28,7 +28,7 @@ import { StatusOfTestrun, TestrunStatus, } from '../model/testrun-status'; -import { device } from '../mocks/device.mock'; +import { device, MOCK_MODULES } from '../mocks/device.mock'; import { NEW_VERSION, VERSION } from '../mocks/version.mock'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { AppState } from '../store/state'; @@ -74,39 +74,23 @@ describe('TestRunService', () => { expect(service).toBeTruthy(); }); - it('should have test modules', () => { - expect(service.getTestModules()).toEqual([ - { - displayName: 'Connection', - name: 'connection', - enabled: true, - }, - { - displayName: 'NTP', - name: 'ntp', - enabled: true, - }, - { - displayName: 'DNS', - name: 'dns', - enabled: true, - }, - { - displayName: 'Services', - name: 'services', - enabled: true, - }, - { - displayName: 'TLS', - name: 'tls', - enabled: true, - }, - { - displayName: 'Protocol', - name: 'protocol', - enabled: true, - }, - ] as TestModule[]); + it('getTestModules should return modules', () => { + let result: string[] = []; + const testModules = MOCK_MODULES; + + service.getTestModules().subscribe(res => { + expect(res).toEqual(result); + }); + + result = testModules; + service.getTestModules(); + const req = httpTestingController.expectOne( + 'http://localhost:8000/system/modules' + ); + + expect(req.request.method).toBe('GET'); + + req.flush(testModules); }); it('fetchDevices should return devices', () => { @@ -284,6 +268,8 @@ describe('TestRunService', () => { const statusesForGreyRes = [ StatusOfTestResult.NotDetected, StatusOfTestResult.NotStarted, + StatusOfTestResult.Skipped, + StatusOfTestResult.Disabled, ]; statusesForGreenRes.forEach(testCase => { diff --git a/modules/ui/src/app/services/test-run.service.ts b/modules/ui/src/app/services/test-run.service.ts index 5620f9404..8d913ba61 100644 --- a/modules/ui/src/app/services/test-run.service.ts +++ b/modules/ui/src/app/services/test-run.service.ts @@ -123,8 +123,10 @@ export class TestRunService { .pipe(map(() => true)); } - getTestModules(): TestModule[] { - return this.testModules; + getTestModules(): Observable { + return this.http + .get(`${API_URL}/system/modules`) + .pipe(catchError(() => of([]))); } saveDevice(device: Device): Observable { @@ -183,7 +185,9 @@ export class TestRunService { result === StatusOfTestResult.InProgress, grey: result === StatusOfTestResult.NotDetected || - result === StatusOfTestResult.NotStarted, + result === StatusOfTestResult.NotStarted || + result === StatusOfTestResult.Skipped || + result === StatusOfTestResult.Disabled, }; } diff --git a/modules/ui/src/app/store/actions.ts b/modules/ui/src/app/store/actions.ts index 3ca38d16f..806618932 100644 --- a/modules/ui/src/app/store/actions.ts +++ b/modules/ui/src/app/store/actions.ts @@ -16,12 +16,13 @@ import { createAction, props } from '@ngrx/store'; import { + Adapters, InterfacesValidation, SettingMissedError, SystemConfig, } from '../model/setting'; import { SystemInterfaces } from '../model/setting'; -import { Device } from '../model/device'; +import { Device, TestModule } from '../model/device'; import { TestrunStatus } from '../model/testrun-status'; import { Profile } from '../model/profile'; @@ -124,3 +125,25 @@ export const setStatus = createAction( export const stopInterval = createAction('[Shared] Stop Interval'); export const fetchRiskProfiles = createAction('[Shared] Fetch risk profiles'); + +export const updateAdapters = createAction( + '[Shared] Update Adapters', + props<{ adapters: Adapters }>() +); + +export const fetchReports = createAction('[Shared] Fetch reports'); + +export const setReports = createAction( + '[Shared] Set Reports', + props<{ reports: TestrunStatus[] }>() +); + +export const setTestModules = createAction( + '[Shared] Set Test Modules', + props<{ testModules: TestModule[] }>() +); + +export const updateInternetConnection = createAction( + '[Shared] Fetch internet connection', + props<{ internetConnection: boolean | null }>() +); diff --git a/modules/ui/src/app/store/effects.spec.ts b/modules/ui/src/app/store/effects.spec.ts index 024782c63..7d33cc209 100644 --- a/modules/ui/src/app/store/effects.spec.ts +++ b/modules/ui/src/app/store/effects.spec.ts @@ -36,12 +36,24 @@ import { import { device } from '../mocks/device.mock'; import { MOCK_PROGRESS_DATA_CANCELLING, + MOCK_PROGRESS_DATA_COMPLIANT, MOCK_PROGRESS_DATA_IN_PROGRESS, MOCK_PROGRESS_DATA_WAITING_FOR_DEVICE, } from '../mocks/testrun.mock'; -import { fetchSystemStatus, setStatus, setTestrunStatus } from './actions'; +import { + fetchSystemStatus, + fetchSystemStatusSuccess, + setReports, + setStatus, + setTestrunStatus, +} from './actions'; import { NotificationService } from '../services/notification.service'; import { PROFILE_MOCK } from '../mocks/profile.mock'; +import { throwError } from 'rxjs/internal/observable/throwError'; +import { HttpErrorResponse } from '@angular/common/http'; +import { IDLE_STATUS } from '../model/testrun-status'; +import { HISTORY } from '../mocks/reports.mock'; +import { TestRunMqttService } from '../services/test-run-mqtt.service'; describe('Effects', () => { let actions$ = new Observable(); @@ -54,6 +66,11 @@ describe('Effects', () => { 'dismissWithTimout', 'openSnackBar', ]); + const mockMqttService: jasmine.SpyObj = + jasmine.createSpyObj('mockMqttService', [ + 'getStatus', + 'getInternetConnection', + ]); beforeEach(() => { testRunServiceMock = jasmine.createSpyObj('testRunServiceMock', [ @@ -64,6 +81,7 @@ describe('Effects', () => { 'testrunInProgress', 'stopTestrun', 'fetchProfiles', + 'getHistory', ]); testRunServiceMock.getSystemInterfaces.and.returnValue(of({})); testRunServiceMock.getSystemConfig.and.returnValue(of({ network: {} })); @@ -72,12 +90,21 @@ describe('Effects', () => { of(MOCK_PROGRESS_DATA_IN_PROGRESS) ); testRunServiceMock.fetchProfiles.and.returnValue(of([])); + testRunServiceMock.getHistory.and.returnValue(of([])); + mockMqttService.getInternetConnection.and.returnValue( + of({ connection: false }) + ); + + mockMqttService.getStatus.and.returnValue( + of(MOCK_PROGRESS_DATA_IN_PROGRESS) + ); TestBed.configureTestingModule({ providers: [ AppEffects, { provide: TestRunService, useValue: testRunServiceMock }, { provide: NotificationService, useValue: notificationServiceMock }, + { provide: TestRunMqttService, useValue: mockMqttService }, provideMockActions(() => actions$), provideMockStore({}), ], @@ -387,14 +414,15 @@ describe('Effects', () => { ); }); - it('should call fetchSystemStatus for status "in progress"', fakeAsync(() => { + it('should call fetchSystemStatus for status "in progress"', () => { effects.onFetchSystemStatusSuccess$.subscribe(() => { - tick(5000); - - expect(dispatchSpy).toHaveBeenCalledWith(fetchSystemStatus()); - discardPeriodicTasks(); + expect(dispatchSpy).toHaveBeenCalledWith( + fetchSystemStatusSuccess({ + systemStatus: MOCK_PROGRESS_DATA_IN_PROGRESS, + }) + ); }); - })); + }); it('should dispatch status and systemStatus', done => { effects.onFetchSystemStatusSuccess$.subscribe(() => { @@ -423,6 +451,12 @@ describe('Effects', () => { done(); }); }); + + it('should call fetchInternetConnection for status "in progress"', () => { + effects.onFetchSystemStatusSuccess$.subscribe(() => { + expect(mockMqttService.getInternetConnection).toHaveBeenCalled(); + }); + }); }); describe('with status "waiting for device"', () => { @@ -439,14 +473,15 @@ describe('Effects', () => { ); }); - it('should call fetchSystemStatus for status "waiting for device"', fakeAsync(() => { + it('should call fetchSystemStatus for status "waiting for device"', () => { effects.onFetchSystemStatusSuccess$.subscribe(() => { - tick(5000); - - expect(dispatchSpy).toHaveBeenCalledWith(fetchSystemStatus()); - discardPeriodicTasks(); + expect(dispatchSpy).toHaveBeenCalledWith( + fetchSystemStatusSuccess({ + systemStatus: MOCK_PROGRESS_DATA_IN_PROGRESS, + }) + ); }); - })); + }); it('should open snackbar when waiting for device is too long', fakeAsync(() => { effects.onFetchSystemStatusSuccess$.subscribe(() => { @@ -487,4 +522,78 @@ describe('Effects', () => { done(); }); }); + + describe('onFetchReports$', () => { + it(' should call setReports on success', done => { + testRunServiceMock.getHistory.and.returnValue(of([])); + actions$ = of(actions.fetchReports()); + + effects.onFetchReports$.subscribe(action => { + expect(action).toEqual( + actions.setReports({ + reports: [], + }) + ); + done(); + }); + }); + + it('should call setReports with empty array if null is returned', done => { + testRunServiceMock.getHistory.and.returnValue(of(null)); + actions$ = of(actions.fetchReports()); + + effects.onFetchReports$.subscribe(action => { + expect(action).toEqual( + actions.setReports({ + reports: [], + }) + ); + done(); + }); + }); + + it('should call setReports with empty array if error happens', done => { + testRunServiceMock.getHistory.and.returnValue( + throwError( + new HttpErrorResponse({ error: { error: 'error' }, status: 500 }) + ) + ); + actions$ = of(actions.fetchReports()); + + effects.onFetchReports$.subscribe({ + complete: () => { + expect(dispatchSpy).toHaveBeenCalledWith( + setReports({ + reports: [], + }) + ); + done(); + }, + }); + }); + }); + + describe('checkStatusInReports$', () => { + it('should call setTestrunStatus if current test run is completed and not present in reports', done => { + store.overrideSelector( + selectSystemStatus, + Object.assign({}, MOCK_PROGRESS_DATA_COMPLIANT, { + mac_addr: '01:02:03:04:05:07', + report: 'http://localhost:8000/report/1234 1234/2024-07-17T15:33:40', + }) + ); + actions$ = of( + actions.setReports({ + reports: HISTORY, + }) + ); + + effects.checkStatusInReports$.subscribe(action => { + expect(action).toEqual( + actions.setTestrunStatus({ systemStatus: IDLE_STATUS }) + ); + done(); + }); + }); + }); }); diff --git a/modules/ui/src/app/store/effects.ts b/modules/ui/src/app/store/effects.ts index b6cdfcc71..5fdb4d461 100644 --- a/modules/ui/src/app/store/effects.ts +++ b/modules/ui/src/app/store/effects.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Injectable, NgZone } from '@angular/core'; +import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; import { map, switchMap, tap, withLatestFrom } from 'rxjs/operators'; @@ -22,29 +22,49 @@ import { map, switchMap, tap, withLatestFrom } from 'rxjs/operators'; import * as AppActions from './actions'; import { AppState } from './state'; import { TestRunService } from '../services/test-run.service'; -import { filter, combineLatest, interval, Subject, timer, take } from 'rxjs'; +import { + filter, + combineLatest, + Subject, + timer, + take, + catchError, + EMPTY, + Subscription, +} from 'rxjs'; import { selectIsOpenWaitSnackBar, selectMenuOpened, selectSystemStatus, } from './selectors'; -import { IResult, StatusOfTestrun, TestsData } from '../model/testrun-status'; +import { + IDLE_STATUS, + IResult, + StatusOfTestrun, + TestrunStatus, + TestsData, +} from '../model/testrun-status'; import { fetchSystemStatus, + fetchSystemStatusSuccess, + setReports, setStatus, setTestrunStatus, stopInterval, + updateInternetConnection, } from './actions'; import { takeUntil } from 'rxjs/internal/operators/takeUntil'; import { NotificationService } from '../services/notification.service'; import { Profile } from '../model/profile'; +import { TestRunMqttService } from '../services/test-run-mqtt.service'; +import { InternetConnection } from '../model/topic'; const WAIT_TO_OPEN_SNACKBAR_MS = 60 * 1000; @Injectable() export class AppEffects { - private startInterval = false; - private destroyInterval$: Subject = new Subject(); + private statusSubscription: Subscription | undefined; + private internetSubscription: Subscription | undefined; private destroyWaitDeviceInterval$: Subject = new Subject(); checkInterfacesInConfig$ = createEffect(() => @@ -190,8 +210,8 @@ export class AppEffects { return this.actions$.pipe( ofType(AppActions.stopInterval), tap(() => { - this.startInterval = false; - this.destroyInterval$.next(true); + this.statusSubscription?.unsubscribe(); + this.internetSubscription?.unsubscribe(); }) ); }, @@ -203,11 +223,9 @@ export class AppEffects { return this.actions$.pipe( ofType(AppActions.fetchSystemStatusSuccess), tap(({ systemStatus }) => { - if ( - this.testrunService.testrunInProgress(systemStatus.status) && - !this.startInterval - ) { + if (this.testrunService.testrunInProgress(systemStatus.status)) { this.pullingSystemStatusData(); + this.fetchInternetConnection(); } else if ( !this.testrunService.testrunInProgress(systemStatus.status) ) { @@ -235,12 +253,10 @@ export class AppEffects { tap(([{ systemStatus }, , status]) => { // for app - requires only status if (systemStatus.status !== status?.status) { - this.ngZone.run(() => { - this.store.dispatch(setStatus({ status: systemStatus.status })); - this.store.dispatch( - setTestrunStatus({ systemStatus: systemStatus }) - ); - }); + this.store.dispatch(setStatus({ status: systemStatus.status })); + this.store.dispatch( + setTestrunStatus({ systemStatus: systemStatus }) + ); } else if ( systemStatus.finished !== status?.finished || (systemStatus.tests as TestsData)?.results?.length !== @@ -248,11 +264,9 @@ export class AppEffects { (systemStatus.tests as IResult[])?.length !== (status?.tests as IResult[])?.length ) { - this.ngZone.run(() => { - this.store.dispatch( - setTestrunStatus({ systemStatus: systemStatus }) - ); - }); + this.store.dispatch( + setTestrunStatus({ systemStatus: systemStatus }) + ); } }) ); @@ -273,6 +287,53 @@ export class AppEffects { ); }); + onFetchReports$ = createEffect(() => { + return this.actions$.pipe( + ofType(AppActions.fetchReports), + switchMap(() => + this.testrunService.getHistory().pipe( + map((reports: TestrunStatus[] | null) => { + if (reports !== null) { + return AppActions.setReports({ reports }); + } + return AppActions.setReports({ reports: [] }); + }), + catchError(() => { + this.store.dispatch(setReports({ reports: [] })); + return EMPTY; + }) + ) + ) + ); + }); + + checkStatusInReports$ = createEffect(() => { + return this.actions$.pipe( + ofType(AppActions.setReports), + withLatestFrom(this.store.select(selectSystemStatus)), + filter(([, systemStatus]) => { + return ( + systemStatus != null && this.isTestrunFinished(systemStatus.status) + ); + }), + filter(([{ reports }, systemStatus]) => { + return ( + !reports?.some(report => report.report === systemStatus!.report) || + false + ); + }), + map(() => AppActions.setTestrunStatus({ systemStatus: IDLE_STATUS })) + ); + }); + + private isTestrunFinished(status: string) { + return ( + status === StatusOfTestrun.Compliant || + status === StatusOfTestrun.NonCompliant || + status === StatusOfTestrun.Error + ); + } + private showSnackBar() { timer(WAIT_TO_OPEN_SNACKBAR_MS) .pipe( @@ -290,22 +351,40 @@ export class AppEffects { } private pullingSystemStatusData(): void { - this.ngZone.runOutsideAngular(() => { - this.startInterval = true; - interval(5000) - .pipe( - takeUntil(this.destroyInterval$), - tap(() => this.store.dispatch(fetchSystemStatus())) - ) - .subscribe(); - }); + if ( + this.statusSubscription === undefined || + this.statusSubscription?.closed + ) { + this.statusSubscription = this.testrunMqttService + .getStatus() + .subscribe(systemStatus => { + this.store.dispatch(fetchSystemStatusSuccess({ systemStatus })); + }); + } + } + + private fetchInternetConnection() { + if ( + this.internetSubscription === undefined || + this.internetSubscription?.closed + ) { + this.internetSubscription = this.testrunMqttService + .getInternetConnection() + .subscribe((internetConnection: InternetConnection) => { + this.store.dispatch( + updateInternetConnection({ + internetConnection: internetConnection.connection, + }) + ); + }); + } } constructor( private actions$: Actions, private testrunService: TestRunService, + private testrunMqttService: TestRunMqttService, private store: Store, - private ngZone: NgZone, private notificationService: NotificationService ) {} } diff --git a/modules/ui/src/app/store/reducers.spec.ts b/modules/ui/src/app/store/reducers.spec.ts index ad611e9f9..b6fe9d675 100644 --- a/modules/ui/src/app/store/reducers.spec.ts +++ b/modules/ui/src/app/store/reducers.spec.ts @@ -25,16 +25,22 @@ import { setIsOpenAddDevice, setIsOpenStartTestrun, setIsOpenWaitSnackBar, + setReports, setRiskProfiles, setStatus, + setTestModules, setTestrunStatus, toggleMenu, + updateAdapters, updateError, updateFocusNavigation, + updateInternetConnection, } from './actions'; -import { device } from '../mocks/device.mock'; +import { device, MOCK_TEST_MODULES } from '../mocks/device.mock'; import { MOCK_PROGRESS_DATA_CANCELLING } from '../mocks/testrun.mock'; import { PROFILE_MOCK } from '../mocks/profile.mock'; +import { HISTORY } from '../mocks/reports.mock'; +import { MOCK_ADAPTERS } from '../mocks/settings.mock'; describe('Reducer', () => { describe('unknown action', () => { @@ -258,4 +264,67 @@ describe('Reducer', () => { expect(state).not.toBe(initialState); }); }); + + describe('setReports action', () => { + it('should update state', () => { + const initialState = initialSharedState; + const action = setReports({ + reports: HISTORY, + }); + const state = fromReducer.sharedReducer(initialState, action); + const newState = { + ...initialState, + ...{ reports: HISTORY }, + }; + + expect(state).toEqual(newState); + expect(state).not.toBe(initialState); + }); + }); + + describe('setTestModules action', () => { + it('should update state', () => { + const initialState = initialSharedState; + const action = setTestModules({ + testModules: MOCK_TEST_MODULES, + }); + const state = fromReducer.sharedReducer(initialState, action); + const newState = { + ...initialState, + ...{ testModules: MOCK_TEST_MODULES }, + }; + + expect(state).toEqual(newState); + expect(state).not.toBe(initialState); + }); + }); + + describe('updateAdapters action', () => { + it('should update state', () => { + const initialState = initialSharedState; + const action = updateAdapters({ + adapters: MOCK_ADAPTERS, + }); + const state = fromReducer.sharedReducer(initialState, action); + const newState = { + ...initialState, + ...{ adapters: MOCK_ADAPTERS }, + }; + + expect(state).toEqual(newState); + expect(state).not.toBe(initialState); + }); + }); + + describe('updateInternetConnection action', () => { + it('should update state', () => { + const initialState = initialSharedState; + const action = updateInternetConnection({ internetConnection: true }); + const state = fromReducer.sharedReducer(initialState, action); + const newState = { ...initialState, ...{ internetConnection: true } }; + + expect(state).toEqual(newState); + expect(state).not.toBe(initialState); + }); + }); }); diff --git a/modules/ui/src/app/store/reducers.ts b/modules/ui/src/app/store/reducers.ts index 501c231a5..dfc54b11f 100644 --- a/modules/ui/src/app/store/reducers.ts +++ b/modules/ui/src/app/store/reducers.ts @@ -106,6 +106,30 @@ export const sharedReducer = createReducer( ...state, status, }; + }), + on(Actions.setReports, (state, { reports }) => { + return { + ...state, + reports, + }; + }), + on(Actions.setTestModules, (state, { testModules }) => { + return { + ...state, + testModules, + }; + }), + on(Actions.updateAdapters, (state, { adapters }) => { + return { + ...state, + adapters, + }; + }), + on(Actions.updateInternetConnection, (state, { internetConnection }) => { + return { + ...state, + internetConnection, + }; }) ); diff --git a/modules/ui/src/app/store/selectors.spec.ts b/modules/ui/src/app/store/selectors.spec.ts index e8d31efc8..facc8bb74 100644 --- a/modules/ui/src/app/store/selectors.spec.ts +++ b/modules/ui/src/app/store/selectors.spec.ts @@ -16,6 +16,7 @@ import { AppState } from './state'; import { + selectAdapters, selectDeviceInProgress, selectDevices, selectError, @@ -27,9 +28,12 @@ import { selectIsOpenStartTestrun, selectIsOpenWaitSnackBar, selectMenuOpened, + selectReports, selectRiskProfiles, selectStatus, selectSystemStatus, + selectTestModules, + selectInternetConnection, } from './selectors'; describe('Selectors', () => { @@ -55,6 +59,10 @@ describe('Selectors', () => { systemStatus: null, deviceInProgress: null, status: null, + reports: [], + testModules: [], + adapters: {}, + internetConnection: null, }, }; @@ -127,4 +135,24 @@ describe('Selectors', () => { const result = selectStatus.projector(initialState); expect(result).toEqual(null); }); + + it('should select status', () => { + const result = selectReports.projector(initialState); + expect(result).toEqual([]); + }); + + it('should select testModules', () => { + const result = selectTestModules.projector(initialState); + expect(result).toEqual([]); + }); + + it('should select adapters', () => { + const result = selectAdapters.projector(initialState); + expect(result).toEqual({}); + }); + + it('should select internetConnection', () => { + const result = selectInternetConnection.projector(initialState); + expect(result).toEqual(null); + }); }); diff --git a/modules/ui/src/app/store/selectors.ts b/modules/ui/src/app/store/selectors.ts index 2f42db3d6..383fee1b9 100644 --- a/modules/ui/src/app/store/selectors.ts +++ b/modules/ui/src/app/store/selectors.ts @@ -93,3 +93,23 @@ export const selectStatus = createSelector( selectAppState, (state: AppState) => state.shared.status ); + +export const selectReports = createSelector( + selectAppState, + (state: AppState) => state.shared.reports +); + +export const selectTestModules = createSelector( + selectAppState, + (state: AppState) => state.shared.testModules +); + +export const selectAdapters = createSelector( + selectAppState, + (state: AppState) => state.shared.adapters +); + +export const selectInternetConnection = createSelector( + selectAppState, + (state: AppState) => state.shared.internetConnection +); diff --git a/modules/ui/src/app/store/state.ts b/modules/ui/src/app/store/state.ts index e2528c5a0..76e2d3254 100644 --- a/modules/ui/src/app/store/state.ts +++ b/modules/ui/src/app/store/state.ts @@ -14,8 +14,12 @@ * limitations under the License. */ import { TestrunStatus } from '../model/testrun-status'; -import { SettingMissedError, SystemInterfaces } from '../model/setting'; -import { Device } from '../model/device'; +import { Device, TestModule } from '../model/device'; +import { + Adapters, + SettingMissedError, + SystemInterfaces, +} from '../model/setting'; import { Profile } from '../model/profile'; export interface AppState { @@ -54,6 +58,10 @@ export interface SharedState { isStopTestrun: boolean; isOpenWaitSnackBar: boolean; deviceInProgress: Device | null; + reports: TestrunStatus[]; + testModules: TestModule[]; + adapters: Adapters; + internetConnection: boolean | null; } export const initialAppComponentState: AppComponentState = { @@ -78,4 +86,8 @@ export const initialSharedState: SharedState = { isOpenStartTestrun: false, systemStatus: null, status: null, + reports: [], + testModules: [], + adapters: {}, + internetConnection: null, }; diff --git a/modules/ui/ui.Dockerfile b/modules/ui/ui.Dockerfile index da56be93e..7ecb32dbd 100644 --- a/modules/ui/ui.Dockerfile +++ b/modules/ui/ui.Dockerfile @@ -12,17 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/ui -FROM node@sha256:ffebb4405810c92d267a764b21975fb2d96772e41877248a37bf3abaa0d3b590 as build - -WORKDIR /modules/ui -COPY modules/ui/ /modules/ui -RUN npm install -RUN npm run build - +# Image name: testrun/ui FROM nginx@sha256:4c0fdaa8b6341bfdeca5f18f7837462c80cff90527ee35ef185571e1c327beac -COPY --from=build /modules/ui/dist/ /usr/share/nginx/html +COPY modules/ui/dist/ /usr/share/nginx/html EXPOSE 8080 diff --git a/modules/ws/conf/mosquitto.conf b/modules/ws/conf/mosquitto.conf new file mode 100644 index 000000000..9027ba814 --- /dev/null +++ b/modules/ws/conf/mosquitto.conf @@ -0,0 +1,22 @@ +## Logging + +log_dest stdout +log_type all +log_timestamp true +connection_messages true + +## MQTT Listener + +listener 1883 +protocol mqtt + +## WebSockets Listener + +listener 9001 +protocol websockets + +allow_anonymous true + +## Persistence + +persistence false \ No newline at end of file diff --git a/modules/ws/ws.Dockerfile b/modules/ws/ws.Dockerfile new file mode 100644 index 000000000..7e9408a47 --- /dev/null +++ b/modules/ws/ws.Dockerfile @@ -0,0 +1,4 @@ +FROM eclipse-mosquitto:2.0.18 +RUN mkdir -p /mosquitto/data/ +COPY modules/ws/conf/mosquitto.conf /mosquitto/config/mosquitto.conf +VOLUME /mosquitto/data/ \ No newline at end of file diff --git a/testing/api/profiles/new_profile.json b/testing/api/profiles/new_profile.json new file mode 100644 index 000000000..d63ecd17c --- /dev/null +++ b/testing/api/profiles/new_profile.json @@ -0,0 +1,54 @@ +{ + "name": "New Profile", + "status": "Valid", + "questions": [ + { + "question": "What type of device is this?", + "answer": "IoT Gateway" + }, + { + "question": "How will this device be used at Google?", + "answer": "Monitoring" + }, + { + "question": "Is this device going to be managed by Google or a third party?", + "answer": "Google" + }, + { + "question": "Will the third-party device administrator be able to grant access to authorized Google personnel upon request?", + "answer": "N/A" + }, + { + "question": "Are any of the following statements true about your device?", + "answer": [ + 0 + ] + }, + { + "question": "Which of the following statements are true about this device?", + "answer": [ + 0 + ] + }, + { + "question": "Does the network protocol assure server-to-client identity verification?", + "answer": "Yes" + }, + { + "question": "Click the statements that best describe the characteristics of this device.", + "answer": [ + 0 + ] + }, + { + "question": "Are any of the following statements true about this device?", + "answer": [ + 0 + ] + }, + { + "question": "Comments", + "answer": "" + } + ] + } \ No newline at end of file diff --git a/testing/api/profiles/new_profile_2.json b/testing/api/profiles/new_profile_2.json new file mode 100644 index 000000000..2ac93dc17 --- /dev/null +++ b/testing/api/profiles/new_profile_2.json @@ -0,0 +1,56 @@ +{ + "name": "New Profile 2", + "status": "Draft", + "questions": [ + { + "question": "What type of device is this?", + "answer": "IoT Gateway" + }, + { + "question": "How will this device be used at Google?", + "answer": "Installed in a building" + }, + { + "question": "Is this device going to be managed by Google or a third party?", + "answer": "Google" + }, + { + "question": "Will the third-party device administrator be able to grant access to authorized Google personnel upon request?", + "answer": "Yes" + }, + { + "question": "Are any of the following statements true about your device?", + "answer": [ + 0, + 2 + ] + }, + { + "question": "Which of the following statements are true about this device?", + "answer": [ + 0, + 1, + 5 + ] + }, + { + "question": "Does the network protocol assure server-to-client identity verification?", + "answer": "Yes" + }, + { + "question": "Click the statements that best describe the characteristics of this device.", + "answer": [ + 0, + 1, + 2 + ] + }, + { + "question": "Are any of the following statements true about this device?", + "answer": [ + 2, + 3 + ] + } + ] +} \ No newline at end of file diff --git a/testing/api/profiles/updated_profile.json b/testing/api/profiles/updated_profile.json new file mode 100644 index 000000000..91714bcfa --- /dev/null +++ b/testing/api/profiles/updated_profile.json @@ -0,0 +1,57 @@ +{ + "name": "New Profile", + "rename": "Updated Profile", + "status": "Draft", + "questions": [ + { + "question": "What type of device is this?", + "answer": "IoT Gateway" + }, + { + "question": "How will this device be used at Google?", + "answer": "Installed in a building" + }, + { + "question": "Is this device going to be managed by Google or a third party?", + "answer": "Google" + }, + { + "question": "Will the third-party device administrator be able to grant access to authorized Google personnel upon request?", + "answer": "Yes" + }, + { + "question": "Are any of the following statements true about your device?", + "answer": [ + 0, + 2 + ] + }, + { + "question": "Which of the following statements are true about this device?", + "answer": [ + 0, + 1, + 5 + ] + }, + { + "question": "Does the network protocol assure server-to-client identity verification?", + "answer": "Yes" + }, + { + "question": "Click the statements that best describe the characteristics of this device.", + "answer": [ + 0, + 1, + 2 + ] + }, + { + "question": "Are any of the following statements true about this device?", + "answer": [ + 2, + 3 + ] + } + ] +} \ No newline at end of file diff --git a/testing/api/test_api.py b/testing/api/test_api.py index 75811e3bb..70c1a617f 100644 --- a/testing/api/test_api.py +++ b/testing/api/test_api.py @@ -29,6 +29,7 @@ import pytest import requests + ALL_DEVICES = "*" API = "http://127.0.0.1:8000" LOG_PATH = "/tmp/testrun.log" @@ -36,7 +37,10 @@ DEVICES_DIRECTORY = "local/devices" TESTING_DEVICES = "../device_configs" +PROFILES_DIRECTORY = "local/risk_profiles" SYSTEM_CONFIG_PATH = "local/system.json" +SYSTEM_CONFIG_RESTORE_PATH = "testing/api/system.json" +PROFILES_PATH = "testing/api/profiles" BASELINE_MAC_ADDR = "02:42:aa:00:01:01" ALL_MAC_ADDR = "02:42:aa:00:00:01" @@ -45,21 +49,18 @@ def pretty_print(dictionary: dict): """ Pretty print dictionary """ print(json.dumps(dictionary, indent=4)) - def query_system_status() -> str: """Query system status from API and returns this""" r = requests.get(f"{API}/system/status", timeout=5) - response = json.loads(r.text) + response = r.json() return response["status"] - def query_test_count() -> int: """Queries status and returns number of test results""" r = requests.get(f"{API}/system/status", timeout=5) - response = json.loads(r.text) + response = r.json() return len(response["tests"]["results"]) - def start_test_device( device_name, mac_address, image_name="test-run/ci_device_1", args="" ): @@ -74,7 +75,6 @@ def start_test_device( ) print(cmd.stdout) - def stop_test_device(device_name): """ Stop docker container with given name """ cmd = subprocess.run( @@ -88,7 +88,6 @@ def stop_test_device(device_name): ) print(cmd.stdout) - def docker_logs(device_name): """ Print docker logs from given docker container name """ cmd = subprocess.run( @@ -97,13 +96,23 @@ def docker_logs(device_name): ) print(cmd.stdout) +def load_json(file_name, directory): + """Utility method to load json files' """ + # Construct the base path relative to the main folder + base_path = Path(__file__).resolve().parent.parent.parent + # Construct the full file path + file_path = base_path / directory / file_name + + # Open the file in read mode + with open(file_path, "r", encoding="utf-8") as file: + # Return the file content + return json.load(file) @pytest.fixture def empty_devices_dir(): """ Use e,pty devices directory """ local_delete_devices(ALL_DEVICES) - @pytest.fixture def testing_devices(): """ Use devices from the testing/device_configs directory """ @@ -115,10 +124,10 @@ def testing_devices(): ) return local_get_devices() - @pytest.fixture def testrun(request): # pylint: disable=W0613 """ Start intstance of testrun """ + # pylint: disable=W1509 with subprocess.Popen( "bin/testrun", stdout=subprocess.PIPE, @@ -165,7 +174,6 @@ def testrun(request): # pylint: disable=W0613 ) print(cmd.stdout) - def until_true(func: Callable, message: str, timeout: int): """ Blocks until given func returns True @@ -179,7 +187,6 @@ def until_true(func: Callable, message: str, timeout: int): time.sleep(1) raise TimeoutError(f"Timed out waiting {timeout}s for {message}") - def dict_paths(thing: dict, stem: str = "") -> Iterator[str]: """Returns json paths (in dot notation) from a given dictionary""" for k, v in thing.items(): @@ -189,7 +196,6 @@ def dict_paths(thing: dict, stem: str = "") -> Iterator[str]: else: yield path - def get_network_interfaces(): """return list of network interfaces on machine @@ -204,7 +210,6 @@ def get_network_interfaces(): ifaces.append(i.stem) return ifaces - def local_delete_devices(path): """ Deletes all local devices """ @@ -214,7 +219,6 @@ def local_delete_devices(path): else: shutil.rmtree(thing) - def local_get_devices(): """ Returns path to device configs of devices in local/devices directory""" return sorted( @@ -223,25 +227,240 @@ def local_get_devices(): ) ) +# Tests for system endpoints + +@pytest.fixture() +def restore_config(): + """Restore the original configuration (system.json) after the test""" + yield + + # Restore system.json from 'testing/api/' after the test + if os.path.exists(SYSTEM_CONFIG_RESTORE_PATH): + shutil.copy(SYSTEM_CONFIG_RESTORE_PATH, SYSTEM_CONFIG_PATH) def test_get_system_interfaces(testrun): # pylint: disable=W0613 """Tests API system interfaces against actual local interfaces""" + + # Send a GET request to the API to retrieve system interfaces r = requests.get(f"{API}/system/interfaces", timeout=5) - response = json.loads(r.text) + + # Check if status code is 200 (OK) + assert r.status_code == 200 + + # Parse the JSON response + response = r.json() + + # Retrieve the actual network interfaces local_interfaces = get_network_interfaces() - assert set(response.keys()) == set(local_interfaces) - # schema expects a flat list + # Check if the key are in the response + assert set(response.keys()) == set(local_interfaces) + # Ensure that all values in the response are strings assert all(isinstance(x, str) for x in response) +def test_update_system_config(testrun, restore_config): # pylint: disable=W0613 + """Test update system configuration endpoint ('/system/config')""" -def test_status_idle(testrun): # pylint: disable=W0613 - until_true( - lambda: query_system_status().lower() == "idle", - "system status is `idle`", - 30, + # Configuration data to update + updated_system_config = { + "network": { + "device_intf": "updated_endev0a", + "internet_intf": "updated_wlan1" + }, + "log_level": "DEBUG" + } + + # Send the post request to update the system configuration + r = requests.post(f"{API}/system/config", + data=json.dumps(updated_system_config), + timeout=5) + + # Check if status code is 200 (OK) + assert r.status_code == 200 + + # Parse the JSON response + response = r.json() + + # Check if the response["network"]["device_intf"] has been updated + assert ( + response["network"]["device_intf"] + == updated_system_config["network"]["device_intf"] + ) + + # Check if the response["network"]["internet_intf"] has been updated + assert ( + response["network"]["internet_intf"] + == updated_system_config["network"]["internet_intf"] + ) + + # Check if the response["log_level"] has been updated + assert ( + response["log_level"] + == updated_system_config["log_level"] + ) + +def test_update_system_config_invalid_config(testrun, restore_config): # pylint: disable=W0613 + """Test invalid configuration file for update system configuration""" + + # Configuration data to update with missing "log_level" field + updated_system_config = { + "network": { + "device_intf": "updated_endev0a", + "internet_intf": "updated_wlan1" + } + } + + # Send the post request to update the system configuration + r = requests.post(f"{API}/system/config", + data=json.dumps(updated_system_config), + timeout=5) + + # Check if status code is 400 (Invalid config) + assert r.status_code == 400 + +def test_get_system_config(testrun): # pylint: disable=W0613 + """Tests get system configuration endpoint ('/system/config')""" + + # Send a GET request to the API to retrieve system configuration + r = requests.get(f"{API}/system/config", timeout=5) + + # Load system configuration file + local_config = load_json("system.json", directory="local") + + # Parse the JSON response + api_config = r.json() + + # Check if status code is 200 (OK) + assert r.status_code == 200 + + # Validate structure + assert set(dict_paths(api_config)) | set(dict_paths(local_config)) == set( + dict_paths(api_config) + ) + + # Check if the device interface in the local config matches the API config + assert ( + local_config["network"]["device_intf"] + == api_config["network"]["device_intf"] + ) + + # Check if the internet interface in the local config matches the API config + assert ( + local_config["network"]["internet_intf"] + == api_config["network"]["internet_intf"] ) +def test_start_testrun_started_successfully(testing_devices, testrun): # pylint: disable=W0613 + """Test for testrun started successfully """ + + # Payload with device details + payload = {"device": { + "mac_addr": BASELINE_MAC_ADDR, + "firmware": "asd", + "test_modules": { + "dns": {"enabled": False}, + "connection": {"enabled": True}, + "ntp": {"enabled": False}, + "baseline": {"enabled": False}, + "nmap": {"enabled": False} + }}} + + # Send the post request + r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) + + # Check if the response status code is 200 (OK) + assert r.status_code == 200 + + # Parse the json response + response = r.json() + + # Check that device is in response + assert "device" in response + + # Check that mac_addr in response + assert "mac_addr" in response["device"] + + # Check that firmware in response + assert "firmware" in response["device"] + +def test_start_testrun_missing_device(testing_devices, testrun): # pylint: disable=W0613 + """Test for missing device when testrun is started """ + + # Payload empty dict (no device) + payload = {} + + # Send the post request + r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) + + # Check if the response status code is 400 (bad request) + assert r.status_code == 400 + + # Parse the json response + response = r.json() + + # Check if 'error' in response + assert "error" in response + +def test_start_testrun_already_started(testing_devices, testrun): # pylint: disable=W0613 + """Test for testrun already started """ + + # Payload with device details + payload = {"device": { + "mac_addr": BASELINE_MAC_ADDR, + "firmware": "asd", + "test_modules": { + "dns": {"enabled": False}, + "connection": {"enabled": True}, + "ntp": {"enabled": False}, + "baseline": {"enabled": False}, + "nmap": {"enabled": False} + }}} + + # Send the post request (start test) + r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) + + # Check if the response status code is 200 (OK) + assert r.status_code == 200 + + # Send the second post request (start test again) + r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) + + # Parse the json response + response = r.json() + + # Check if the response status code is 409 (Conflict) + assert r.status_code == 409 + + # Check if 'error' in response + assert "error" in response + +def test_start_testrun_device_not_found(testing_devices, testrun): # pylint: disable=W0613 + """Test for start testrun device not found """ + + # Payload with device details with no mac address assigned + payload = {"device": { + "mac_addr": "", + "firmware": "asd", + "test_modules": { + "dns": {"enabled": False}, + "connection": {"enabled": True}, + "ntp": {"enabled": False}, + "baseline": {"enabled": False}, + "nmap": {"enabled": False} + }}} + + # Send the post request + r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) + + # Check if the response status code is 404 (not found) + assert r.status_code == 404 + + # Parse the json response + response = r.json() + + # Check if 'error' in response + assert "error" in response + # Currently not working due to blocking during monitoring period @pytest.mark.skip() def test_status_in_progress(testing_devices, testrun): # pylint: disable=W0613 @@ -264,22 +483,35 @@ def test_status_in_progress(testing_devices, testrun): # pylint: disable=W0613 600, ) - +# Currently not working due to blocking during monitoring period @pytest.mark.skip() -def test_status_non_compliant(testing_devices, testrun): # pylint: disable=W0613 +def test_start_testrun_already_in_progress( + testing_devices, # pylint: disable=W0613 + testrun): # pylint: disable=W0613 + payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}} + r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) - r = requests.get(f"{API}/devices", timeout=5) - all_devices = json.loads(r.text) - payload = { - "device": { - "mac_addr": all_devices[0]["mac_addr"], - "firmware": "asd" - } - } - r = requests.post(f"{API}/system/start", data=json.dumps(payload), - timeout=10) + until_true( + lambda: query_system_status().lower() == "waiting for device", + "system status is `waiting for device`", + 30, + ) + + start_test_device("x123", BASELINE_MAC_ADDR) + + until_true( + lambda: query_system_status().lower() == "in progress", + "system status is `in progress`", + 600, + ) + r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) + assert r.status_code == 409 + +@pytest.mark.skip() +def test_trigger_run(testing_devices, testrun): # pylint: disable=W0613 + payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}} + r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) assert r.status_code == 200 - print(r.text) until_true( lambda: query_system_status().lower() == "waiting for device", @@ -287,102 +519,350 @@ def test_status_non_compliant(testing_devices, testrun): # pylint: disable=W0613 30, ) - start_test_device("x123", all_devices[0]["mac_addr"]) + start_test_device("x123", BASELINE_MAC_ADDR) until_true( - lambda: query_system_status().lower() == "non-compliant", - "system status is `complete", + lambda: query_system_status().lower() == "compliant", + "system status is `complete`", 600, ) stop_test_device("x123") -def test_create_get_devices(empty_devices_dir, testrun): # pylint: disable=W0613 - device_1 = { - "manufacturer": "Google", - "model": "First", - "mac_addr": "00:1e:42:35:73:c4", - "test_modules": { - "dns": {"enabled": True}, - "connection": {"enabled": True}, - "ntp": {"enabled": True}, - "baseline": {"enabled": True}, - "nmap": {"enabled": True}, - }, - } - - r = requests.post(f"{API}/device", data=json.dumps(device_1), - timeout=5) - print(r.text) - assert r.status_code == 201 - assert len(local_get_devices()) == 1 - - device_2 = { - "manufacturer": "Google", - "model": "Second", - "mac_addr": "00:1e:42:35:73:c6", - "test_modules": { - "dns": {"enabled": True}, - "connection": {"enabled": True}, - "ntp": {"enabled": True}, - "baseline": {"enabled": True}, - "nmap": {"enabled": True}, - }, - } - r = requests.post(f"{API}/device", data=json.dumps(device_2), - timeout=5) - assert r.status_code == 201 - assert len(local_get_devices()) == 2 + # Validate response + r = requests.get(f"{API}/system/status", timeout=5) + response = r.json() + pretty_print(response) - # Test that returned devices API endpoint matches expected structure - r = requests.get(f"{API}/devices", timeout=5) - all_devices = json.loads(r.text) - pretty_print(all_devices) + # Validate results + results = {x["name"]: x for x in response["tests"]["results"]} + print(results) + # there are only 3 baseline tests + assert len(results) == 3 + # Validate structure with open( - os.path.join(os.path.dirname(__file__), "mockito/get_devices.json"), - encoding="utf-8" + os.path.join( + os.path.dirname(__file__), "mockito/running_system_status.json" + ), encoding="utf-8" ) as f: mockito = json.load(f) - print(mockito) - - # Validate structure - assert all(isinstance(x, dict) for x in all_devices) + # validate structure + assert set(dict_paths(mockito)).issubset(set(dict_paths(response))) - # TOOO uncomment when is done - # assert set(dict_paths(mockito[0])) == set(dict_paths(all_devices[0])) + # Validate results structure + assert set(dict_paths(mockito["tests"]["results"][0])).issubset( + set(dict_paths(response["tests"]["results"][0])) + ) - # Validate contents of given keys matches - for key in ["mac_addr", "manufacturer", "model"]: - assert set([all_devices[0][key], all_devices[1][key]]) == set( - [device_1[key], device_2[key]] - ) + # Validate a result + assert results["baseline.compliant"]["result"] == "Compliant" +@pytest.mark.skip() +def test_stop_running_test(testing_devices, testrun): # pylint: disable=W0613 + payload = {"device": {"mac_addr": ALL_MAC_ADDR, "firmware": "asd"}} + r = requests.post(f"{API}/system/start", data=json.dumps(payload), + timeout=10) + assert r.status_code == 200 -def test_delete_device_success(empty_devices_dir, testrun): # pylint: disable=W0613 - device_1 = { - "manufacturer": "Google", - "model": "First", - "mac_addr": "00:1e:42:35:73:c4", - "test_modules": { - "dns": {"enabled": True}, - "connection": {"enabled": True}, - "ntp": {"enabled": True}, - "baseline": {"enabled": True}, - "nmap": {"enabled": True}, - }, - } + until_true( + lambda: query_system_status().lower() == "waiting for device", + "system status is `waiting for device`", + 30, + ) - # Send create device request - r = requests.post(f"{API}/device", - data=json.dumps(device_1), - timeout=5) - print(r.text) + start_test_device("x12345", ALL_MAC_ADDR) - # Check device has been created - assert r.status_code == 201 - assert len(local_get_devices()) == 1 + until_true( + lambda: query_test_count() > 1, + "system status is `complete`", + 1000, + ) + + stop_test_device("x12345") + + # Validate response + r = requests.post(f"{API}/system/stop", timeout=5) + response = r.json() + pretty_print(response) + assert response == {"success": "Testrun stopped"} + time.sleep(1) + + # Validate response + r = requests.get(f"{API}/system/status", timeout=5) + response = r.json() + pretty_print(response) + + assert response["status"] == "Cancelled" + +def test_stop_running_not_running(testrun): # pylint: disable=W0613 + # Validate response + r = requests.post(f"{API}/system/stop", + timeout=10) + response = r.json() + pretty_print(response) + + assert r.status_code == 404 + assert response["error"] == "Testrun is not currently running" + +@pytest.mark.skip() +def test_multiple_runs(testing_devices, testrun): # pylint: disable=W0613 + payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}} + r = requests.post(f"{API}/system/start", data=json.dumps(payload), + timeout=10) + assert r.status_code == 200 + print(r.text) + + until_true( + lambda: query_system_status().lower() == "waiting for device", + "system status is `waiting for device`", + 30, + ) + + start_test_device("x123", BASELINE_MAC_ADDR) + + until_true( + lambda: query_system_status().lower() == "compliant", + "system status is `complete`", + 900, + ) + + stop_test_device("x123") + + # Validate response + r = requests.get(f"{API}/system/status", timeout=5) + response = r.json() + pretty_print(response) + + # Validate results + results = {x["name"]: x for x in response["tests"]["results"]} + print(results) + # there are only 3 baseline tests + assert len(results) == 3 + + payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}} + r = requests.post(f"{API}/system/start", data=json.dumps(payload), + timeout=10) + # assert r.status_code == 200 + # returns 409 + print(r.text) + + until_true( + lambda: query_system_status().lower() == "waiting for device", + "system status is `waiting for device`", + 30, + ) + + start_test_device("x123", BASELINE_MAC_ADDR) + + until_true( + lambda: query_system_status().lower() == "compliant", + "system status is `complete`", + 900, + ) + + stop_test_device("x123") + +def test_status_idle(testrun): # pylint: disable=W0613 + """Test system status 'idle' endpoint (/system/status)""" + until_true( + lambda: query_system_status().lower() == "idle", + "system status is `idle`", + 30, + ) + +def test_system_shutdown(testrun): # pylint: disable=W0613 + """Test the shutdown system endpoint""" + # Send a POST request to initiate the system shutdown + r = requests.post(f"{API}/system/shutdown", timeout=5) + + # Check if the response status code is 200 (OK) + assert r.status_code == 200, f"Expected status code 200, got {r.status_code}" + +def test_system_shutdown_in_progress(testrun): # pylint: disable=W0613 + """Test system shutdown during an in-progress test""" + # Payload with device details to start a test + payload = { + "device": { + "mac_addr": BASELINE_MAC_ADDR, + "firmware": "asd", + "test_modules": { + "dns": {"enabled": False}, + "connection": {"enabled": True}, + "ntp": {"enabled": False}, + "baseline": {"enabled": False}, + "nmap": {"enabled": False} + } + } + } + + # Start a test + r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) + + # Check if status code is not 200 (OK) + if r.status_code != 200: + raise ValueError(f"Api request failed with code: {r.status_code}") + + # Attempt to shutdown while the test is running + r = requests.post(f"{API}/system/shutdown", timeout=5) + + # Check if the response status code is 400 (test in progress) + assert r.status_code == 400 + +def test_system_latest_version(testrun): # pylint: disable=W0613 + """Test for testrun version when the latest version is installed""" + + # Send the get request to the API + r = requests.get(f"{API}/system/version", timeout=5) + + # Parse the response + response = r.json() + + # Check if status code is 200 (update available) + assert r.status_code == 200 + # Check if an update is available + assert response["update_available"] is False + +# Tests for reports endpoints + +def test_get_reports_no_reports(testrun): # pylint: disable=W0613 + """Test get reports when no reports exist.""" + + # Send a GET request to the /reports endpoint + r = requests.get(f"{API}/reports", timeout=5) + + # Check if the status code is 200 (OK) + assert r.status_code == 200 + + # Parse the JSON response + response = r.json() + + # Check if the response is a list + assert isinstance(response, list) + + # Check if the response is an empty list + assert response == [] + +# Tests for device endpoints + +@pytest.mark.skip() +def test_status_non_compliant(testing_devices, testrun): # pylint: disable=W0613 + + r = requests.get(f"{API}/devices", timeout=5) + all_devices = r.json() + payload = { + "device": { + "mac_addr": all_devices[0]["mac_addr"], + "firmware": "asd" + } + } + r = requests.post(f"{API}/system/start", data=json.dumps(payload), + timeout=10) + assert r.status_code == 200 + print(r.text) + + until_true( + lambda: query_system_status().lower() == "waiting for device", + "system status is `waiting for device`", + 30, + ) + + start_test_device("x123", all_devices[0]["mac_addr"]) + + until_true( + lambda: query_system_status().lower() == "non-compliant", + "system status is `complete", + 600, + ) + + stop_test_device("x123") + +def test_create_get_devices(empty_devices_dir, testrun): # pylint: disable=W0613 + device_1 = { + "manufacturer": "Google", + "model": "First", + "mac_addr": "00:1e:42:35:73:c4", + "test_modules": { + "dns": {"enabled": True}, + "connection": {"enabled": True}, + "ntp": {"enabled": True}, + "baseline": {"enabled": True}, + "nmap": {"enabled": True}, + }, + } + + r = requests.post(f"{API}/device", data=json.dumps(device_1), + timeout=5) + print(r.text) + assert r.status_code == 201 + assert len(local_get_devices()) == 1 + + device_2 = { + "manufacturer": "Google", + "model": "Second", + "mac_addr": "00:1e:42:35:73:c6", + "test_modules": { + "dns": {"enabled": True}, + "connection": {"enabled": True}, + "ntp": {"enabled": True}, + "baseline": {"enabled": True}, + "nmap": {"enabled": True}, + }, + } + r = requests.post(f"{API}/device", data=json.dumps(device_2), + timeout=5) + assert r.status_code == 201 + assert len(local_get_devices()) == 2 + + # Test that returned devices API endpoint matches expected structure + r = requests.get(f"{API}/devices", timeout=5) + all_devices = r.json() + pretty_print(all_devices) + + with open( + os.path.join(os.path.dirname(__file__), "mockito/get_devices.json"), + encoding="utf-8" + ) as f: + mockito = json.load(f) + + print(mockito) + + # Validate structure + assert all(isinstance(x, dict) for x in all_devices) + + # TOOO uncomment when is done + # assert set(dict_paths(mockito[0])) == set(dict_paths(all_devices[0])) + + # Validate contents of given keys matches + for key in ["mac_addr", "manufacturer", "model"]: + assert set([all_devices[0][key], all_devices[1][key]]) == set( + [device_1[key], device_2[key]] + ) + +def test_delete_device_success(empty_devices_dir, testrun): # pylint: disable=W0613 + device_1 = { + "manufacturer": "Google", + "model": "First", + "mac_addr": "00:1e:42:35:73:c4", + "test_modules": { + "dns": {"enabled": True}, + "connection": {"enabled": True}, + "ntp": {"enabled": True}, + "baseline": {"enabled": True}, + "nmap": {"enabled": True}, + }, + } + + # Send create device request + r = requests.post(f"{API}/device", + data=json.dumps(device_1), + timeout=5) + print(r.text) + + # Check device has been created + assert r.status_code == 201 + assert len(local_get_devices()) == 1 device_2 = { "manufacturer": "Google", @@ -413,7 +893,7 @@ def test_delete_device_success(empty_devices_dir, testrun): # pylint: disable=W0 # Test that returned devices API endpoint matches expected structure r = requests.get(f"{API}/devices", timeout=5) - all_devices = json.loads(r.text) + all_devices = r.json() pretty_print(all_devices) with open( @@ -437,7 +917,6 @@ def test_delete_device_success(empty_devices_dir, testrun): # pylint: disable=W0 [device_2[key]] ) - def test_delete_device_not_found(empty_devices_dir, testrun): # pylint: disable=W0613 device_1 = { "manufacturer": "Google", @@ -476,7 +955,6 @@ def test_delete_device_not_found(empty_devices_dir, testrun): # pylint: disable= assert r.status_code == 404 assert len(local_get_devices()) == 0 - def test_delete_device_no_mac(empty_devices_dir, testrun): # pylint: disable=W0613 device_1 = { "manufacturer": "Google", @@ -510,7 +988,6 @@ def test_delete_device_no_mac(empty_devices_dir, testrun): # pylint: disable=W06 assert r.status_code == 400 assert len(local_get_devices()) == 1 - # Currently not working due to blocking during monitoring period @pytest.mark.skip() def test_delete_device_testrun_running(testing_devices, testrun): # pylint: disable=W0613 @@ -550,41 +1027,8 @@ def test_delete_device_testrun_running(testing_devices, testrun): # pylint: disa timeout=5) assert r.status_code == 403 - -def test_start_testrun_started_successfully( - testing_devices, # pylint: disable=W0613 - testrun): # pylint: disable=W0613 - payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}} - r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) - assert r.status_code == 200 - - -# Currently not working due to blocking during monitoring period -@pytest.mark.skip() -def test_start_testrun_already_in_progress( - testing_devices, # pylint: disable=W0613 - testrun): # pylint: disable=W0613 - payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}} - r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) - - until_true( - lambda: query_system_status().lower() == "waiting for device", - "system status is `waiting for device`", - 30, - ) - - start_test_device("x123", BASELINE_MAC_ADDR) - - until_true( - lambda: query_system_status().lower() == "in progress", - "system status is `in progress`", - 600, - ) - r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) - assert r.status_code == 409 - -def test_start_system_not_configured_correctly( - empty_devices_dir, # pylint: disable=W0613 +def test_start_system_not_configured_correctly( + empty_devices_dir, # pylint: disable=W0613 testrun): # pylint: disable=W0613 device_1 = { "manufacturer": "Google", @@ -611,7 +1055,6 @@ def test_start_system_not_configured_correctly( timeout=10) assert r.status_code == 500 - def test_start_device_not_found(empty_devices_dir, # pylint: disable=W0613 testrun): # pylint: disable=W0613 device_1 = { @@ -644,7 +1087,6 @@ def test_start_device_not_found(empty_devices_dir, # pylint: disable=W0613 timeout=10) assert r.status_code == 404 - def test_start_missing_device_information( empty_devices_dir, # pylint: disable=W0613 testrun): # pylint: disable=W0613 @@ -673,7 +1115,6 @@ def test_start_missing_device_information( timeout=10) assert r.status_code == 400 - def test_create_device_already_exists( empty_devices_dir, # pylint: disable=W0613 testrun): # pylint: disable=W0613 @@ -703,7 +1144,6 @@ def test_create_device_already_exists( print(r.text) assert r.status_code == 409 - def test_create_device_invalid_json( empty_devices_dir, # pylint: disable=W0613 testrun): # pylint: disable=W0613 @@ -716,7 +1156,6 @@ def test_create_device_invalid_json( print(r.text) assert r.status_code == 400 - def test_create_device_invalid_request( empty_devices_dir, # pylint: disable=W0613 testrun): # pylint: disable=W0613 @@ -727,7 +1166,6 @@ def test_create_device_invalid_request( print(r.text) assert r.status_code == 400 - def test_device_edit_device( testing_devices, # pylint: disable=W0613 testrun): # pylint: disable=W0613 @@ -740,7 +1178,7 @@ def test_device_edit_device( new_model = "Alphabet" r = requests.get(f"{API}/devices", timeout=5) - all_devices = json.loads(r.text) + all_devices = r.json() api_device = next(x for x in all_devices if x["mac_addr"] == mac_addr) @@ -770,13 +1208,12 @@ def test_device_edit_device( assert r.status_code == 200 r = requests.get(f"{API}/devices", timeout=5) - all_devices = json.loads(r.text) + all_devices = r.json() updated_device_api = next(x for x in all_devices if x["mac_addr"] == mac_addr) assert updated_device_api["model"] == new_model assert updated_device_api["test_modules"] == new_test_modules - def test_device_edit_device_not_found( empty_devices_dir, # pylint: disable=W0613 testrun): # pylint: disable=W0613 @@ -814,7 +1251,6 @@ def test_device_edit_device_not_found( assert r.status_code == 404 - def test_device_edit_device_incorrect_json_format( empty_devices_dir, # pylint: disable=W0613 testrun): # pylint: disable=W0613 @@ -847,7 +1283,6 @@ def test_device_edit_device_incorrect_json_format( assert r.status_code == 400 - def test_device_edit_device_with_mac_already_exists( empty_devices_dir, # pylint: disable=W0613 testrun): # pylint: disable=W0613 @@ -904,42 +1339,9 @@ def test_device_edit_device_with_mac_already_exists( assert r.status_code == 409 - -def test_system_latest_version(testrun): # pylint: disable=W0613 - r = requests.get(f"{API}/system/version", timeout=5) - assert r.status_code == 200 - updated_system_version = json.loads(r.text)["update_available"] - assert updated_system_version is False - -def test_get_system_config(testrun): # pylint: disable=W0613 - r = requests.get(f"{API}/system/config", timeout=5) - - with open( - SYSTEM_CONFIG_PATH, - encoding="utf-8" - ) as f: - local_config = json.load(f) - - api_config = json.loads(r.text) - - # validate structure - assert set(dict_paths(api_config)) | set(dict_paths(local_config)) == set( - dict_paths(api_config) - ) - - assert ( - local_config["network"]["device_intf"] - == api_config["network"]["device_intf"] - ) - assert ( - local_config["network"]["internet_intf"] - == api_config["network"]["internet_intf"] - ) - - def test_invalid_path_get(testrun): # pylint: disable=W0613 r = requests.get(f"{API}/blah/blah", timeout=5) - response = json.loads(r.text) + response = r.json() assert r.status_code == 404 with open( os.path.join(os.path.dirname(__file__), "mockito/invalid_request.json"), @@ -950,188 +1352,484 @@ def test_invalid_path_get(testrun): # pylint: disable=W0613 # validate structure assert set(dict_paths(mockito)) == set(dict_paths(response)) +def test_create_invalid_chars(empty_devices_dir, testrun): # pylint: disable=W0613 + # local_delete_devices(ALL_DEVICES) + # We must start test run with no devices in local/devices for this test + # to function as expected + assert len(local_get_devices()) == 0 -@pytest.mark.skip() -def test_trigger_run(testing_devices, testrun): # pylint: disable=W0613 - payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}} - r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) + # Test adding device + device_1 = { + "manufacturer": "/'disallowed characters///", + "model": "First", + "mac_addr": BASELINE_MAC_ADDR, + "test_modules": { + "dns": {"enabled": False}, + "connection": {"enabled": True}, + "ntp": {"enabled": True}, + "baseline": {"enabled": True}, + "nmap": {"enabled": True}, + }, + } + + r = requests.post(f"{API}/device", data=json.dumps(device_1), + timeout=5) + print(r.text) + print(r.status_code) + +def test_get_test_modules(testrun): # pylint: disable=W0613 + """Test the /system/modules endpoint to get the test modules""" + + # Send a GET request to the API endpoint + r = requests.get(f"{API}/system/modules", timeout=5) + + # Check if status code is 200 (OK) assert r.status_code == 200 - until_true( - lambda: query_system_status().lower() == "waiting for device", - "system status is `waiting for device`", - 30, - ) + # Parse the JSON response + response = r.json() + + # Check if the response is a list + assert isinstance(response, list) + +# Tests for profile endpoints +def delete_all_profiles(): + """Utility method to delete all profiles from risk_profiles folder""" + + # Assign the profiles directory + profiles_path = Path(PROFILES_DIRECTORY) + + try: + # Check if the profile_path (local/risk_profiles) exists and is a folder + if profiles_path.exists() and profiles_path.is_dir(): + # Iterate over all profiles from risk_profiles folder + for item in profiles_path.iterdir(): + # Check if item is a file + if item.is_file(): + #If True remove file + item.unlink() + else: + # If item is a folder remove it + shutil.rmtree(item) + + except PermissionError: + # Permission related issues + print(f"Permission Denied: {item}") + except OSError as err: + # System related issues + print(f"Error removing {item}: {err}") + +def create_profile(file_name): + """Utility method to create the profile""" + + # Load the profile + new_profile = load_json(file_name, directory=PROFILES_PATH) + + # Assign the profile name to profile_name + profile_name = new_profile["name"] + + # Exception if the profile already exists + if profile_exists(profile_name): + raise ValueError(f"Profile: {profile_name} exists") + + # Send the post request + r = requests.post(f"{API}/profiles", data=json.dumps(new_profile), timeout=5) + + # Exception if status code is not 201 + if r.status_code != 201: + raise ValueError(f"API request failed with code: {r.status_code}") + + # Return the profile + return new_profile + +@pytest.fixture() +def reset_profiles(): + """Delete the profiles before and after each test""" + + # Delete before the test + delete_all_profiles() + + yield + + # Delete after the test + delete_all_profiles() + +@pytest.fixture() +def add_profile(): + """Fixture to create profiles during tests.""" + # Returning the reference to create_profile + return create_profile + +def profile_exists(profile_name): + """Utility method to check if profile exists""" + # Send the get request + r = requests.get(f"{API}/profiles", timeout=5) + # Check if status code is not 200 (OK) + if r.status_code != 200: + raise ValueError(f"Api request failed with code: {r.status_code}") + # Parse the JSON response to get the list of profiles + profiles = r.json() + # Return if name is in the list of profiles + return any(p["name"] == profile_name for p in profiles) + +def test_get_profiles_format(testrun): # pylint: disable=W0613 + """Test profiles format""" + + # Send the get request + r = requests.get(f"{API}/profiles/format", timeout=5) + + # Check if status code is 200 (OK) + assert r.status_code == 200 - start_test_device("x123", BASELINE_MAC_ADDR) + # Parse the response + response = r.json() - until_true( - lambda: query_system_status().lower() == "compliant", - "system status is `complete`", - 600, - ) + # Check if the response is a list + assert isinstance(response, list) - stop_test_device("x123") + # Check that each item in the response has keys "questions" and "type" + for item in response: + assert "question" in item + assert "type" in item - # Validate response - r = requests.get(f"{API}/system/status", timeout=5) - response = json.loads(r.text) - pretty_print(response) +def test_get_profiles(testrun, reset_profiles, add_profile): # pylint: disable=W0613 + """Test for get profiles (no profile, one profile, two profiles)""" - # Validate results - results = {x["name"]: x for x in response["tests"]["results"]} - print(results) - # there are only 3 baseline tests - assert len(results) == 3 + # Test for no profiles - # Validate structure - with open( - os.path.join( - os.path.dirname(__file__), "mockito/running_system_status.json" - ), encoding="utf-8" - ) as f: - mockito = json.load(f) + # Send the get request to "/profiles" endpoint + r = requests.get(f"{API}/profiles", timeout=5) - # validate structure - assert set(dict_paths(mockito)).issubset(set(dict_paths(response))) + # Check if status code is 200 (OK) + assert r.status_code == 200 - # Validate results structure - assert set(dict_paths(mockito["tests"]["results"][0])).issubset( - set(dict_paths(response["tests"]["results"][0])) - ) + # Parse the response (profiles) + response = r.json() - # Validate a result - assert results["baseline.compliant"]["result"] == "Compliant" + # Check if response is a list + assert isinstance(response, list) + # Check if the list is empty + assert len(response) == 0 -@pytest.mark.skip() -def test_stop_running_test(testing_devices, testrun): # pylint: disable=W0613 - payload = {"device": {"mac_addr": ALL_MAC_ADDR, "firmware": "asd"}} - r = requests.post(f"{API}/system/start", data=json.dumps(payload), - timeout=10) + # Test for one profile + + # Load the profile using add_profile fixture + add_profile("new_profile.json") + + # Send get request to the "/profiles" endpoint + r = requests.get(f"{API}/profiles", timeout=5) + + # Check if status code is 200 (OK) assert r.status_code == 200 - until_true( - lambda: query_system_status().lower() == "waiting for device", - "system status is `waiting for device`", - 30, - ) + # Parse the response (profiles) + response = r.json() - start_test_device("x12345", ALL_MAC_ADDR) + # Check if response is a list + assert isinstance(response, list) - until_true( - lambda: query_test_count() > 1, - "system status is `complete`", - 1000, - ) + # Check if response contains one profile + assert len(response) == 1 - stop_test_device("x12345") + # Check that each profile has the expected fields + for profile in response: + for field in ["name", "status", "created", "version", "questions", "risk"]: + assert field in profile - # Validate response - r = requests.post(f"{API}/system/stop", timeout=5) - response = json.loads(r.text) - pretty_print(response) - assert response == {"success": "Testrun stopped"} - time.sleep(1) + # Check if "questions" value is a list + assert isinstance(profile["questions"], list) - # Validate response - r = requests.get(f"{API}/system/status", timeout=5) - response = json.loads(r.text) - pretty_print(response) + # Check that "questions" value has the expected fields + for element in profile["questions"]: + # Check if each element is dict + assert isinstance(element, dict) - assert response["status"] == "Cancelled" + # Check if "question" key is in dict element + assert "question" in element + # Check if "asnswer" key is in dict element + assert "answer" in element -def test_stop_running_not_running(testrun): # pylint: disable=W0613 - # Validate response - r = requests.post(f"{API}/system/stop", - timeout=10) - response = json.loads(r.text) - pretty_print(response) + # Test for two profiles - assert r.status_code == 404 - assert response["error"] == "Testrun is not currently running" + # Load the profile using add_profile fixture + add_profile("new_profile_2.json") -@pytest.mark.skip() -def test_multiple_runs(testing_devices, testrun): # pylint: disable=W0613 - payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}} - r = requests.post(f"{API}/system/start", data=json.dumps(payload), - timeout=10) + # Send the get request to "/profiles" endpoint + r = requests.get(f"{API}/profiles", timeout=5) + + # Parse the response (profiles) + response = r.json() + + # Check if status code is 200 (OK) assert r.status_code == 200 - print(r.text) - until_true( - lambda: query_system_status().lower() == "waiting for device", - "system status is `waiting for device`", - 30, - ) + # Check if response is a list + assert isinstance(response, list) - start_test_device("x123", BASELINE_MAC_ADDR) + # Check if response contains two profiles + assert len(response) == 2 - until_true( - lambda: query_system_status().lower() == "compliant", - "system status is `complete`", - 900, +def test_create_profile(testrun, reset_profiles): # pylint: disable=W0613 + """Test for create profile if not exists""" + + # Load the profile + new_profile = load_json("new_profile.json", directory=PROFILES_PATH) + + # Assign the profile name to profile_name + profile_name = new_profile["name"] + + # Check if the profile already exists + if profile_exists(profile_name): + raise ValueError(f"Profile: {profile_name} exists") + + # Send the post request + r = requests.post(f"{API}/profiles", data=json.dumps(new_profile), timeout=5) + + # Check if status code is 201 (Created) + assert r.status_code == 201 + + # Parse the response + response = r.json() + + # Check if "success" key in response + assert "success" in response + + # Verify profile creation + r = requests.get(f"{API}/profiles", timeout=5) + + # Check if status code is 200 (OK) + assert r.status_code == 200 + + # Parse the response + profiles = r.json() + + # Iterate through all the profiles to find the profile based on the "name" + created_profile = next( + (p for p in profiles if p["name"] == profile_name), None ) - stop_test_device("x123") + # Check if profile was created + assert created_profile is not None - # Validate response - r = requests.get(f"{API}/system/status", timeout=5) - response = json.loads(r.text) - pretty_print(response) +def test_update_profile(testrun, reset_profiles, add_profile): # pylint: disable=W0613 + """Test for update profile when exists""" - # Validate results - results = {x["name"]: x for x in response["tests"]["results"]} - print(results) - # there are only 3 baseline tests - assert len(results) == 3 + # Load the new profile using add_profile fixture + new_profile = add_profile("new_profile.json") - payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}} - r = requests.post(f"{API}/system/start", data=json.dumps(payload), - timeout=10) - # assert r.status_code == 200 - # returns 409 - print(r.text) + # Load the updated profile using load_json utility method + updated_profile = load_json("updated_profile.json", + directory=PROFILES_PATH) - until_true( - lambda: query_system_status().lower() == "waiting for device", - "system status is `waiting for device`", - 30, + # Assign the new_profile name + profile_name = new_profile["name"] + + # Assign the updated_profile name + updated_profile_name = updated_profile["rename"] + + # Exception if the profile does't exists + if not profile_exists(profile_name): + raise ValueError(f"Profile: {profile_name} exists") + + # Send the post request to update the profile + r = requests.post( + f"{API}/profiles", + data=json.dumps(updated_profile), + timeout=5) + + # Check if status code is 200 (OK) + assert r.status_code == 200 + + # Parse the response + response = r.json() + + # Check if "success" key in response + assert "success" in response + + # Get request to verify profile update + r = requests.get(f"{API}/profiles", timeout=5) + + # Check if status code is 200 (OK) + assert r.status_code == 200 + + # Parse the response + profiles = r.json() + + # Iterate through the profiles to find the profile based on the updated "name" + updated_profile_check = next( + (p for p in profiles if p["name"] == updated_profile_name), + None ) + # Check if profile was updated + assert updated_profile_check is not None - start_test_device("x123", BASELINE_MAC_ADDR) +def test_update_profile_invalid_json(testrun, reset_profiles, add_profile): # pylint: disable=W0613 + """Test for update profile invalid JSON payload (no 'name')""" - until_true( - lambda: query_system_status().lower() == "compliant", - "system status is `complete`", - 900, + # Load the new profile using add_profile fixture + add_profile("new_profile.json") + + # invalid JSON + updated_profile = {} + + # Send the post request to update the profile + r = requests.post( + f"{API}/profiles", + data=json.dumps(updated_profile), + timeout=5) + + # Parse the response + response = r.json() + + # Check if status code is 400 (Bad request) + assert r.status_code == 400 + + # Check if "error" key in response + assert "error" in response + +def test_create_profile_invalid_json(testrun, reset_profiles): # pylint: disable=W0613 + """Test for create profile invalid JSON payload """ + + # invalid JSON + new_profile = {} + + # Send the post request to update the profile + r = requests.post( + f"{API}/profiles", + data=json.dumps(new_profile), + timeout=5) + + # Parse the response + response = r.json() + + # Check if status code is 400 (Bad request) + assert r.status_code == 400 + + # Check if "error" key in response + assert "error" in response + +def test_delete_profile(testrun, reset_profiles, add_profile): # pylint: disable=W0613 + """Test for delete profile""" + + # Assign the profile from the fixture + profile_to_delete = add_profile("new_profile.json") + + # Assign the profile name + profile_name = profile_to_delete["name"] + + # Delete the profile + r = requests.delete( + f"{API}/profiles", + data=json.dumps(profile_to_delete), + timeout=5) + + # Check if status code is 200 (OK) + assert r.status_code == 200 + + # Parse the JSON response + response = r.json() + + # Check if the response contains "success" key + assert "success" in response + + # Check if the profile has been deleted + r = requests.get(f"{API}/profiles", timeout=5) + + # Check if status code is 200 (OK) + assert r.status_code == 200 + + # Parse the JSON response + profiles = r.json() + + # Iterate through the profiles to find the profile based on the "name" + deleted_profile = next( + (p for p in profiles if p["name"] == profile_name), + None ) + # Check if profile was deleted + assert deleted_profile is None - stop_test_device("x123") +def test_delete_profile_no_profile(testrun, reset_profiles): # pylint: disable=W0613 + """Test delete profile if the profile does not exists""" + # Assign the profile to delete + profile_to_delete = {"name": "New Profile"} -def test_create_invalid_chars(empty_devices_dir, testrun): # pylint: disable=W0613 - # local_delete_devices(ALL_DEVICES) - # We must start test run with no devices in local/devices for this test - # to function as expected - assert len(local_get_devices()) == 0 + # Delete the profile + r = requests.delete( + f"{API}/profiles", + data=json.dumps(profile_to_delete), + timeout=5) - # Test adding device - device_1 = { - "manufacturer": "/'disallowed characters///", - "model": "First", - "mac_addr": BASELINE_MAC_ADDR, - "test_modules": { - "dns": {"enabled": False}, - "connection": {"enabled": True}, - "ntp": {"enabled": True}, - "baseline": {"enabled": True}, - "nmap": {"enabled": True}, - }, - } + # Check if status code is 404 (Profile does not exist) + assert r.status_code == 404 - r = requests.post(f"{API}/device", data=json.dumps(device_1), - timeout=5) - print(r.text) - print(r.status_code) +def test_delete_profile_invalid_json(testrun, reset_profiles): # pylint: disable=W0613 + """Test for delete profile wrong JSON payload""" + + profile_to_delete = {} + + # Delete the profile + r = requests.delete( + f"{API}/profiles", + data=json.dumps(profile_to_delete), + timeout=5) + + # Parse the response + response = r.json() + + # Check if status code is 400 (bad request) + assert r.status_code == 400 + + # Check if "error" key in response + assert "error" in response + + profile_to_delete_2 = {"status": "Draft"} + # Delete the profile + r = requests.delete( + f"{API}/profiles", + data=json.dumps(profile_to_delete_2), + timeout=5) + + # Parse the response + response = r.json() + + # Check if status code is 400 (bad request) + assert r.status_code == 400 + + # Check if "error" key in response + assert "error" in response + +def test_delete_profile_internal_server_error(testrun, # pylint: disable=W0613 + reset_profiles, # pylint: disable=W0613 + add_profile ): + """Test for delete profile causing internal server error""" + + # Assign the profile from the fixture + profile_to_delete = add_profile("new_profile.json") + + # Assign the profile name to profile_name + profile_name = profile_to_delete["name"] + + # Construct the path to the profile JSON file in local/risk_profiles + risk_profile_path = os.path.join(PROFILES_DIRECTORY, f"{profile_name}.json") + + # Delete the profile JSON file before making the DELETE request + if os.path.exists(risk_profile_path): + os.remove(risk_profile_path) + + # Send the DELETE request to delete the profile + r = requests.delete(f"{API}/profiles", + json={"name": profile_to_delete["name"]}, + timeout=5) + + # Check if status code is 500 (Internal Server Error) + assert r.status_code == 500 + + # Parse the json response + response = r.json() + + # Check if error in response + assert "error" in response diff --git a/testing/pylint/test_pylint b/testing/pylint/test_pylint index 1f71482e5..7e102c7f8 100755 --- a/testing/pylint/test_pylint +++ b/testing/pylint/test_pylint @@ -14,27 +14,35 @@ # See the License for the specific language governing permissions and # limitations under the License. -ERROR_LIMIT=25 - -sudo cmd/install +# Install python venv +python3 -m venv venv +# Activate the venv source venv/bin/activate -sudo pip3 install pylint==3.0.3 +# Install pylint +pip install pylint==3.2.6 + +# Declare the applicable files files=$(find . -path ./venv -prune -o -name '*.py' -print) +# Define the pylint output file OUT=pylint.out -rm -f $OUT && touch $OUT +# Remove it if it already exists +rm -f $OUT +# Run pylint against the target files +# Change the evaluation to total the number of errors +# Output to the specified output file pylint $files -ry --extension-pkg-allow-list=docker --evaluation="error + warning + refactor + convention" 2>/dev/null | tee -a $OUT -new_errors=$(cat $OUT | grep -oP "(?!=^Your code has been rated at)([0-9]+)(?=\.00/10[ \(]?)" ) +# Obtain the total number of errors from the pylint out file +errors=$(cat $OUT | grep -oP "(?!=^Your code has been rated at)([0-9]+)(?=\.00/10[ \(]?)" ) -echo "$new_errors > $ERROR_LIMIT?" -if (( $new_errors > $ERROR_LIMIT)); then - echo new errors $new_errors > error limit $ERROR_LIMIT - echo failing .. +# Check if any errors exist +if (( $errors > 0 )); then + echo "$errors pylint issues have been identified. These must be resolved before merging." exit 1 fi diff --git a/testing/tests/test_tests.py b/testing/tests/test_tests.py index aaae1a09d..21be6b7de 100644 --- a/testing/tests/test_tests.py +++ b/testing/tests/test_tests.py @@ -96,7 +96,7 @@ def test_list_tests(capsys, results, test_matrix): print('============') print('============') print('tests seen:') - print('\n'.join(set([x.name for x in all_tests]))) + print('\n'.join(set(x.name for x in all_tests))) print('\ntesting for pass:') print('\n'.join(ci_pass)) print('\ntesting for fail:') diff --git a/testing/unit/conn/captures/monitor.pcap b/testing/unit/conn/captures/monitor.pcap new file mode 100644 index 0000000000000000000000000000000000000000..0dfb85ff4a04427badd206f3110992dfdfcd51d4 GIT binary patch literal 389089 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 nn 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

tD_>x2GWaDj0FGA@yu! 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 literal 0 HcmV?d00001 diff --git a/testing/unit/conn/conn_module_test.py b/testing/unit/conn/conn_module_test.py index d31a8051f..906abb754 100644 --- a/testing/unit/conn/conn_module_test.py +++ b/testing/unit/conn/conn_module_test.py @@ -13,13 +13,17 @@ # limitations under the License. """Module run all the Connection module related unit tests""" from port_stats_util import PortStatsUtil +from connection_module import ConnectionModule import os import unittest from common import logger MODULE = 'conn' -# Define the file paths -TEST_FILES_DIR = 'testing/unit/' + MODULE +# Define the directories +TEST_FILES_DIR = '/testing/unit/' + MODULE +OUTPUT_DIR = os.path.join(TEST_FILES_DIR, 'output/') +CAPTURES_DIR = os.path.join(TEST_FILES_DIR, 'captures/') + ETHTOOL_RESULTS_COMPLIANT_FILE = os.path.join(TEST_FILES_DIR, 'ethtool', 'ethtool_results_compliant.txt') ETHTOOL_RESULTS_NONCOMPLIANT_FILE = os.path.join( @@ -34,8 +38,12 @@ ETHTOOL_PORT_STATS_POST_NONCOMPLIANT_FILE = os.path.join( TEST_FILES_DIR, 'ethtool', 'ethtool_port_stats_post_monitor_noncompliant.txt') -LOGGER = None +# Define the capture files to be used for the test +STARTUP_CAPTURE_FILE = os.path.join(CAPTURES_DIR, 'startup.pcap') +MONITOR_CAPTURE_FILE = os.path.join(CAPTURES_DIR, 'monitor.pcap') + +LOGGER = None class ConnectionModuleTest(unittest.TestCase): """Contains and runs all the unit tests concerning Connection @@ -46,6 +54,9 @@ def setUpClass(cls): global LOGGER LOGGER = logger.get_logger('unit_test_' + MODULE) + # Set the MAC address for device in capture files + os.environ['DEVICE_MAC'] = '98:f0:7b:d1:87:06' + # Test the port link status def connection_port_link_compliant_test(self): LOGGER.info('connection_port_link_compliant_test') @@ -117,6 +128,17 @@ def connection_port_speed_autonegotiation_fail_test(self): LOGGER.info(result) self.assertEqual(result[0], False) + # Test proper filtering for ICMP protocol in DHCP packets + def connection_switch_dhcp_snooping_icmp_test(self): + LOGGER.info('connection_switch_dhcp_snooping_icmp_test') + conn_module = ConnectionModule(module=MODULE, + log_dir=OUTPUT_DIR, + results_dir=OUTPUT_DIR, + startup_capture_file=STARTUP_CAPTURE_FILE, + monitor_capture_file=MONITOR_CAPTURE_FILE) + result = conn_module._connection_switch_dhcp_snooping() # pylint: disable=W0212 + LOGGER.info(result) + self.assertEqual(result[0], True) if __name__ == '__main__': suite = unittest.TestSuite() @@ -136,5 +158,9 @@ def connection_port_speed_autonegotiation_fail_test(self): suite.addTest( ConnectionModuleTest('connection_port_speed_autonegotiation_fail_test')) + # DHCP Snooping related tests + suite.addTest( + ConnectionModuleTest('connection_switch_dhcp_snooping_icmp_test')) + runner = unittest.TextTestRunner() runner.run(suite) diff --git a/testing/unit/dns/dns_module_test.py b/testing/unit/dns/dns_module_test.py index 6c3dec74d..a4b7a81e9 100644 --- a/testing/unit/dns/dns_module_test.py +++ b/testing/unit/dns/dns_module_test.py @@ -16,7 +16,6 @@ import unittest from scapy.all import rdpcap, DNS, wrpcap import os -from testreport import TestReport MODULE = 'dns' @@ -28,7 +27,6 @@ LOCAL_REPORT = os.path.join(REPORTS_DIR, 'dns_report_local.html') LOCAL_REPORT_NO_DNS = os.path.join(REPORTS_DIR, 'dns_report_local_no_dns.html') -CONF_FILE = 'modules/test/' + MODULE + '/conf/module_config.json' # Define the capture files to be used for the test DNS_SERVER_CAPTURE_FILE = os.path.join(CAPTURES_DIR, 'dns.pcap') @@ -44,11 +42,13 @@ def setUpClass(cls): # Create the output directories and ignore errors if it already exists os.makedirs(OUTPUT_DIR, exist_ok=True) + # Set the MAC address for device in capture files + os.environ['DEVICE_MAC'] = '38:d1:35:01:17:fe' + # Test the module report generation def dns_module_report_test(self): dns_module = DNSModule(module=MODULE, log_dir=OUTPUT_DIR, - conf_file=CONF_FILE, results_dir=OUTPUT_DIR, dns_server_capture_file=DNS_SERVER_CAPTURE_FILE, startup_capture_file=STARTUP_CAPTURE_FILE, @@ -59,12 +59,6 @@ def dns_module_report_test(self): # Read the generated report with open(report_out_path, 'r', encoding='utf-8') as file: report_out = file.read() - formatted_report = self.add_formatting(report_out) - - # Write back the new formatted_report value - out_report_path = os.path.join(OUTPUT_DIR, 'dns_report_with_dns.html') - with open(out_report_path, 'w', encoding='utf-8') as file: - file.write(formatted_report) # Read the local good report with open(LOCAL_REPORT, 'r', encoding='utf-8') as file: @@ -104,7 +98,6 @@ def dns_module_report_no_dns_test(self): dns_module = DNSModule(module='dns', log_dir=OUTPUT_DIR, - conf_file=CONF_FILE, results_dir=OUTPUT_DIR, dns_server_capture_file=dns_server_cap_file, startup_capture_file=startup_cap_file, @@ -115,12 +108,6 @@ def dns_module_report_no_dns_test(self): # Read the generated report with open(report_out_path, 'r', encoding='utf-8') as file: report_out = file.read() - formatted_report = self.add_formatting(report_out) - - # Write back the new formatted_report value - out_report_path = os.path.join(OUTPUT_DIR, 'dns_report_no_dns.html') - with open(out_report_path, 'w', encoding='utf-8') as file: - file.write(formatted_report) # Read the local good report with open(LOCAL_REPORT_NO_DNS, 'r', encoding='utf-8') as file: @@ -128,17 +115,6 @@ def dns_module_report_no_dns_test(self): self.assertEqual(report_out, report_local) - def add_formatting(self, body): - return f''' - - - {TestReport().generate_head()} - - {body} - - DNS Module

Requests to local DNS server Requests to external DNS servers Total DNS requests Total DNS responses
71 6 77 91
Source Destination Type URL
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 8.8.8.8 Query mqtt.googleapis.com
10.10.10.4 8.8.8.8 Query mqtt.googleapis.com
8.8.8.8 10.10.10.4 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
8.8.8.8 10.10.10.4 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query pool.ntp.org
10.10.10.14 10.10.10.4 Query pool.ntp.org
10.10.10.4 8.8.8.8 Query pool.ntp.org
10.10.10.4 8.8.8.8 Query pool.ntp.org
8.8.8.8 10.10.10.4 Response pool.ntp.org
10.10.10.4 10.10.10.14 Response pool.ntp.org
8.8.8.8 10.10.10.4 Response pool.ntp.org
10.10.10.4 10.10.10.14 Response pool.ntp.org
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query pool.ntp.org
10.10.10.14 10.10.10.4 Query pool.ntp.org
10.10.10.4 8.8.8.8 Query pool.ntp.org
10.10.10.4 10.10.10.14 Response pool.ntp.org
10.10.10.4 10.10.10.14 Response pool.ntp.org
8.8.8.8 10.10.10.4 Response pool.ntp.org
10.10.10.4 8.8.8.8 Response pool.ntp.org
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 8.8.8.8 Query mqtt.googleapis.com
8.8.8.8 10.10.10.4 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query pool.ntp.org
10.10.10.14 10.10.10.4 Query pool.ntp.org
10.10.10.4 10.10.10.14 Response pool.ntp.org
10.10.10.4 10.10.10.14 Response pool.ntp.org
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query pool.ntp.org
10.10.10.4 10.10.10.14 Response pool.ntp.org
10.10.10.4 10.10.10.14 Response pool.ntp.org
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
\ No newline at end of file +

DNS Module

Requests to local DNS server Requests to external DNS servers Total DNS requests Total DNS responses
71 0 71 84
Source Destination Type URL Count
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com 64
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com 76
10.10.10.14 10.10.10.4 Query pool.ntp.org 7
10.10.10.4 10.10.10.14 Response pool.ntp.org 8
\ No newline at end of file diff --git a/testing/unit/framework/session_test.py b/testing/unit/framework/session_test.py new file mode 100644 index 000000000..1045457f3 --- /dev/null +++ b/testing/unit/framework/session_test.py @@ -0,0 +1,57 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Session methods tests""" + +from unittest.mock import patch +from common import session + + +class MockUtil: + """mock util functions""" + + @staticmethod + def get_sys_interfaces(): + return {"eth0": "00:1A:2B:3C:4D:5E", "eth1": "66:77:88:99:AA:BB"} + + @staticmethod + def diff_dicts(d1, d2): # pylint: disable=W0613 + return { + "items_added": {"eth1": "66:77:88:99:AA:BB"}, + "items_removed": {"eth2": "00:1B:2C:3D:4E:5F"}, + } + + +class TestrunSessionMock(session.TestrunSession): + def __init__(self): # pylint: disable=W0231 + self._ifaces = {"eth0": "00:1A:2B:3C:4D:5E", "eth2": "66:77:88:99:AA:BB"} + + +util = MockUtil() + + +@patch("common.util.get_sys_interfaces", side_effect=util.get_sys_interfaces) +@patch("common.util.diff_dicts", side_effect=util.diff_dicts) +def test_detect_network_adapters_change( + mock_get_sys_interfaces, # pylint: disable=W0613 + mock_diff_dicts, # pylint: disable=W0613 +): + testrun_session = TestrunSessionMock() + + # Test added and removed + result = testrun_session.detect_network_adapters_change() + assert result == { + "adapters_added": {"eth1": "66:77:88:99:AA:BB"}, + "adapters_removed": {"eth2": "00:1B:2C:3D:4E:5F"}, + } diff --git a/testing/unit/framework/util_test.py b/testing/unit/framework/util_test.py new file mode 100644 index 000000000..ec8fd48fc --- /dev/null +++ b/testing/unit/framework/util_test.py @@ -0,0 +1,61 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Util tests""" + +from collections import namedtuple +from unittest.mock import patch +from common import util +from net_orc import ip_control + +Snicaddr = namedtuple('snicaddr', + ['family', 'address']) + +mock_addrs = { + 'eth0': [Snicaddr(17, '00:1A:2B:3C:4D:5E')], + 'wlan0': [Snicaddr(17, '66:77:88:99:AA:BB')], + 'enp0s3': [Snicaddr(17, '11:22:33:44:55:66')] +} + +@patch('psutil.net_if_addrs') +def test_get_sys_interfaces(mock_net_if_addrs): + mock_net_if_addrs.return_value = mock_addrs + # Expected result + expected = { + 'eth0': '00:1A:2B:3C:4D:5E', + 'enp0s3': '11:22:33:44:55:66' + } + + result = ip_control.IPControl.get_sys_interfaces() + # Assert the result + assert result == expected + + +def test_diff_dicts(): + d1 = {'a': 1, 'b': 2} + d2 = {'a': 1, 'b': 2} + #Assert equal dicts + assert not util.diff_dicts(d1, d2) + d2 = {'a': 1, 'c': 3} + expected = {'items_removed': {'b': 2},'items_added': {'c': 3}} + #Assert items added adn removed + assert util.diff_dicts(d1, d2) == expected + d1 = {'a': 1} + d2 = {'b': 2} + expected = { + 'items_removed': {'a': 1}, + 'items_added': {'b': 2} + } + #Assert completely different dicts + assert util.diff_dicts(d1, d2) == expected diff --git a/testing/unit/ntp/ntp_module_test.py b/testing/unit/ntp/ntp_module_test.py index 20dd88ef1..ac10bb46c 100644 --- a/testing/unit/ntp/ntp_module_test.py +++ b/testing/unit/ntp/ntp_module_test.py @@ -11,12 +11,11 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""Module run all the DNS related unit tests""" +"""Module run all the NTP related unit tests""" from ntp_module import NTPModule import unittest from scapy.all import rdpcap, NTP, wrpcap import os -from testreport import TestReport MODULE = 'ntp' @@ -28,7 +27,6 @@ LOCAL_REPORT = os.path.join(REPORTS_DIR,'ntp_report_local.html') LOCAL_REPORT_NO_NTP = os.path.join(REPORTS_DIR,'ntp_report_local_no_ntp.html') -CONF_FILE = 'modules/test/' + MODULE + '/conf/module_config.json' # Define the capture files to be used for the test NTP_SERVER_CAPTURE_FILE = os.path.join(CAPTURES_DIR,'ntp.pcap') @@ -49,7 +47,6 @@ def setUpClass(cls): def ntp_module_report_test(self): ntp_module = NTPModule(module=MODULE, log_dir=OUTPUT_DIR, - conf_file=CONF_FILE, results_dir=OUTPUT_DIR, ntp_server_capture_file=NTP_SERVER_CAPTURE_FILE, startup_capture_file=STARTUP_CAPTURE_FILE, @@ -60,12 +57,6 @@ def ntp_module_report_test(self): # Read the generated report with open(report_out_path, 'r', encoding='utf-8') as file: report_out = file.read() - formatted_report = self.add_formatting(report_out) - - # Write back the new formatted_report value - out_report_path = os.path.join(OUTPUT_DIR, 'ntp_report_with_ntp.html') - with open(out_report_path, 'w', encoding='utf-8') as file: - file.write(formatted_report) # Read the local good report with open(LOCAL_REPORT, 'r', encoding='utf-8') as file: @@ -105,7 +96,6 @@ def ntp_module_report_no_ntp_test(self): ntp_module = NTPModule(module='dns', log_dir=OUTPUT_DIR, - conf_file=CONF_FILE, results_dir=OUTPUT_DIR, ntp_server_capture_file=ntp_server_cap_file, startup_capture_file=startup_cap_file, @@ -116,12 +106,6 @@ def ntp_module_report_no_ntp_test(self): # Read the generated report with open(report_out_path, 'r', encoding='utf-8') as file: report_out = file.read() - formatted_report = self.add_formatting(report_out) - - # Write back the new formatted_report value - out_report_path = os.path.join(OUTPUT_DIR,'ntp_report_no_ntp.html') - with open(out_report_path, 'w', encoding='utf-8') as file: - file.write(formatted_report) # Read the local good report with open(LOCAL_REPORT_NO_NTP, 'r', encoding='utf-8') as file: @@ -129,16 +113,6 @@ def ntp_module_report_no_ntp_test(self): self.assertEqual(report_out, report_local) - def add_formatting(self,body): - return f''' - - - {TestReport().generate_head()} - - {body} - - NTP Module 101 104 + @@ -24,1444 +25,90 @@

NTP Module

- + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + + - + + - + + - - - - - - - - - - - - - - - + + - + + - + +
Destination Type VersionTimestampCountSync Request Average
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:29.447
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:29.448
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:31.577
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:31.577
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:33.694
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:33.694
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:35.785
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:35.786
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:37.806
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:37.806
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:39.856
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:39.856
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:41.931
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:41.932
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:43.954
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:43.956
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:13:06.439
10.10.10.510.10.10.15Server4Feb 15, 2024 22:13:06.439
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:13:08.492
10.10.10.510.10.10.15Server4Feb 15, 2024 22:13:08.494
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:13:40.536
10.10.10.510.10.10.15Server4Feb 15, 2024 22:13:40.541
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:13:48.274
10.10.10.510.10.10.15Server4Feb 15, 2024 22:13:48.277
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:14:12.619
10.10.10.510.10.10.15Server4Feb 15, 2024 22:14:12.624
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:14:44.702
10.10.10.510.10.10.15Server4Feb 15, 2024 22:14:44.703
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:14:53.026
10.10.10.510.10.10.15Server4Feb 15, 2024 22:14:53.029
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:15:16.786
10.10.10.510.10.10.15Server4Feb 15, 2024 22:15:16.791
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:15:48.884
10.10.10.510.10.10.15Server4Feb 15, 2024 22:15:48.887
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:15:57.829
10.10.10.510.10.10.15Server4Feb 15, 2024 22:15:57.829
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:16:20.970
10.10.10.510.10.10.15Server4Feb 15, 2024 22:16:20.970
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:16:54.054
10.10.10.510.10.10.15Server4Feb 15, 2024 22:16:54.054
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:17:02.738
10.10.10.510.10.10.15Server4Feb 15, 2024 22:17:02.740
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:17:26.136
10.10.10.510.10.10.15Server4Feb 15, 2024 22:17:26.139
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:17:59.293
10.10.10.510.10.10.15Server4Feb 15, 2024 22:17:59.293
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:18:07.242
10.10.10.510.10.10.15Server4Feb 15, 2024 22:18:07.242
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:18:32.379
10.10.10.510.10.10.15Server4Feb 15, 2024 22:18:32.379
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:20:06.908
10.10.10.510.10.10.15Server4Feb 15, 2024 22:20:06.908
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:20:08.936
10.10.10.510.10.10.15Server4Feb 15, 2024 22:20:08.937
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:20:10.974
10.10.10.510.10.10.15Server4Feb 15, 2024 22:20:10.974
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:20:12.998
10.10.10.510.10.10.15Server4Feb 15, 2024 22:20:12.999
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:20:59.581
10.10.10.510.10.10.15Server4Feb 15, 2024 22:20:59.582
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:21:34.063
10.10.10.510.10.10.15Server4Feb 15, 2024 22:21:34.063
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:21:36.121
10.10.10.510.10.10.15Server4Feb 15, 2024 22:21:36.121
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:21:38.176
10.10.10.510.10.10.15Server4Feb 15, 2024 22:21:38.176
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:21:40.277
10.10.10.510.10.10.15Server4Feb 15, 2024 22:21:40.277
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:22:05.704
10.10.10.510.10.10.15Server4Feb 15, 2024 22:22:05.706
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:22:45.469
10.10.10.510.10.10.15Server4Feb 15, 2024 22:22:45.470
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:23:09.826
10.10.10.510.10.10.15Server4Feb 15, 2024 22:23:09.828
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:23:50.337
10.10.10.510.10.10.15Server4Feb 15, 2024 22:23:50.343
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:24:13.945
10.10.10.510.10.10.15Server4Feb 15, 2024 22:24:13.946
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:24:54.876
10.10.10.510.10.10.15Server4Feb 15, 2024 22:24:54.877
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:25:59.000
10.10.10.510.10.10.15Server4Feb 15, 2024 22:25:59.001
10.10.10.15216.239.35.12Client4Feb 15, 2024 22:12:28.681
216.239.35.1210.10.10.15Server4Feb 15, 2024 22:12:28.728
10.10.10.15216.239.35.4Client4Feb 15, 2024 22:12:28.842
216.239.35.410.10.10.15Server4Feb 15, 2024 22:12:28.888
10.10.10.15216.239.35.8Client4Feb 15, 2024 22:12:29.042
216.239.35.810.10.10.15Server4Feb 15, 2024 22:12:29.089
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:12:29.243
216.239.35.010.10.10.15Server4Feb 15, 2024 22:12:29.290
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:29.447
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:29.448
10.10.10.15216.239.35.12Client4Feb 15, 2024 22:12:30.802
216.239.35.1210.10.10.15Server4Feb 15, 2024 22:12:30.850
10.10.10.15216.239.35.4Client4Feb 15, 2024 22:12:30.973
216.239.35.410.10.10.15Server4Feb 15, 2024 22:12:31.032
10.10.10.15216.239.35.8Client4Feb 15, 2024 22:12:31.173
216.239.35.810.10.10.15Server4Feb 15, 2024 22:12:31.220
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:12:31.376
216.239.35.010.10.10.15Server4Feb 15, 2024 22:12:31.423
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:31.577
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:31.577
10.10.10.15216.239.35.12Client4Feb 15, 2024 22:12:32.867
216.239.35.1210.10.10.15Server4Feb 15, 2024 22:12:32.914
10.10.10.15216.239.35.4Client4Feb 15, 2024 22:12:33.112
216.239.35.410.10.10.15Server4Feb 15, 2024 22:12:33.159
10.10.10.15216.239.35.8Client4Feb 15, 2024 22:12:33.271
216.239.35.810.10.10.15Server4Feb 15, 2024 22:12:33.318
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:12:33.475
216.239.35.010.10.10.15Server4Feb 15, 2024 22:12:33.522
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:33.694
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:33.694
10.10.10.15216.239.35.12Client4Feb 15, 2024 22:12:34.956
216.239.35.1210.10.10.15Server4Feb 15, 2024 22:12:35.002
10.10.10.15216.239.35.4Client4Feb 15, 2024 22:12:35.182
216.239.35.410.10.10.15Server4Feb 15, 2024 22:12:35.228
10.10.10.15216.239.35.8Client4Feb 15, 2024 22:12:35.398
216.239.35.810.10.10.15Server4Feb 15, 2024 22:12:35.445
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:12:35.625
216.239.35.010.10.10.15Server4Feb 15, 2024 22:12:35.673
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:35.785
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:35.786
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:37.806
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:37.806
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:39.856
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:39.856
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:41.931
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:41.932
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:43.954
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:43.956
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:13:06.439
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:13:06.439
10.10.10.510.10.10.15Server4Feb 15, 2024 22:13:06.439
216.239.35.010.10.10.15Server4Feb 15, 2024 22:13:06.489
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:13:08.492
10.10.10.510.10.10.15Server4Feb 15, 2024 22:13:08.494
216.239.35.010.10.10.15Server4Feb 15, 2024 22:13:08.543
10.10.10.15216.239.35.12Client4Feb 15, 2024 22:13:40.310
216.239.35.1210.10.10.15Server4Feb 15, 2024 22:13:40.357
10.10.10.15216.239.35.4Client4Feb 15, 2024 22:13:40.512
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:13:40.536
10.10.10.510.10.10.15Server4Feb 15, 2024 22:13:40.542
216.239.35.410.10.10.15Server4Feb 15, 2024 22:13:40.574
216.239.35.010.10.10.15Server4Feb 15, 2024 22:13:40.583
10.10.10.15216.239.35.8Client4Feb 15, 2024 22:13:40.714
216.239.35.810.10.10.15Server4Feb 15, 2024 22:13:40.764
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:13:40.917
216.239.35.010.10.10.15Server4Feb 15, 2024 22:13:40.965
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:13:48.274
10.10.10.510.10.10.15Server4Feb 15, 2024 22:13:48.277
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:14:12.619
10.10.10.510.10.10.15Server4Feb 15, 2024 22:14:12.624
216.239.35.010.10.10.15Server4Feb 15, 2024 22:14:12.668
10.10.10.15216.239.35.12Client4Feb 15, 2024 22:14:44.515
216.239.35.1210.10.10.15Server4Feb 15, 2024 22:14:44.562
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:14:44.702
10.10.10.510.10.10.15Server4Feb 15, 2024 22:14:44.704
10.10.10.15216.239.35.4Client4Feb 15, 2024 22:14:45.158
216.239.35.410.10.10.15Server4Feb 15, 2024 22:14:45.219
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:14:45.359
216.239.35.010.10.10.15Server4Feb 15, 2024 22:14:45.406
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:14:45.707
216.239.35.010.10.10.15Server4Feb 15, 2024 22:14:45.755
10.10.10.15216.239.35.8Client4Feb 15, 2024 22:14:45.980
216.239.35.810.10.10.15Server4Feb 15, 2024 22:14:46.027
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:14:53.026
10.10.10.510.10.10.15Server4Feb 15, 2024 22:14:53.029
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:15:16.786
10.10.10.510.10.10.15Server4Feb 15, 2024 22:15:16.791
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:15:18.794
216.239.35.010.10.10.15Server4Feb 15, 2024 22:15:18.843
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:15:48.884
10.10.10.510.10.10.15Server4Feb 15, 2024 22:15:48.887
10.10.10.15 216.239.35.12 Client 4Feb 15, 2024 22:15:49.063837.942 seconds
216.239.35.12 10.10.10.15 Server 4Feb 15, 2024 22:15:49.110
10.10.10.15216.239.35.4Client4Feb 15, 2024 22:15:49.462
216.239.35.410.10.10.15Server4Feb 15, 2024 22:15:49.509
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:15:50.127
216.239.35.010.10.10.15Server4Feb 15, 2024 22:15:50.175
10.10.10.15216.239.35.8Client4Feb 15, 2024 22:15:51.107
216.239.35.810.10.10.15Server4Feb 15, 2024 22:15:51.154
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:15:51.890
216.239.35.010.10.10.15Server4Feb 15, 2024 22:15:51.938
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:15:57.829
10.10.10.510.10.10.15Server4Feb 15, 2024 22:15:57.829
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:16:20.970
10.10.10.510.10.10.15Server4Feb 15, 2024 22:16:20.971
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:16:24.975
216.239.35.010.10.10.15Server4Feb 15, 2024 22:16:25.0238N/A
10.10.10.15 216.239.35.4 Client 4Feb 15, 2024 22:16:53.677837.834 seconds
216.239.35.4 10.10.10.15 Server 4Feb 15, 2024 22:16:53.739
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:16:54.054
10.10.10.510.10.10.15Server4Feb 15, 2024 22:16:54.054
10.10.10.15216.239.35.12Client4Feb 15, 2024 22:16:54.276
216.239.35.1210.10.10.15Server4Feb 15, 2024 22:16:54.322
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:16:54.593
216.239.35.010.10.10.15Server4Feb 15, 2024 22:16:54.6488N/A
10.10.10.15 216.239.35.8 Client 4Feb 15, 2024 22:16:55.435838.056 seconds
216.239.35.8 10.10.10.15 Server 4Feb 15, 2024 22:16:55.4818N/A
10.10.10.15 216.239.35.0 Client 4Feb 15, 2024 22:16:57.0591420.601 seconds
216.239.35.0 10.10.10.15 Server 4Feb 15, 2024 22:16:57.107
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:17:02.738
10.10.10.510.10.10.15Server4Feb 15, 2024 22:17:02.74017N/A
10.10.10.15 10.10.10.5 Client 4Feb 15, 2024 22:17:26.1366313.057 seconds
10.10.10.5 10.10.10.15 Server 4Feb 15, 2024 22:17:26.13963N/A
diff --git a/testing/unit/ntp/reports/ntp_report_local_no_ntp.html b/testing/unit/ntp/reports/ntp_report_local_no_ntp.html index 7df0fbd87..7fe2e6ab5 100644 --- a/testing/unit/ntp/reports/ntp_report_local_no_ntp.html +++ b/testing/unit/ntp/reports/ntp_report_local_no_ntp.html @@ -15,6 +15,7 @@

NTP Module

0 0 +
diff --git a/testing/unit/protocol/protocol_module_test.py b/testing/unit/protocol/protocol_module_test.py index 32a0021cd..6ba3143c0 100644 --- a/testing/unit/protocol/protocol_module_test.py +++ b/testing/unit/protocol/protocol_module_test.py @@ -46,7 +46,6 @@ def setUpClass(cls): BACNET = BACnet(log=LOGGER, captures_dir=CAPTURES_DIR, capture_file='bacnet.pcap', - bin_dir='modules/test/protocol/bin', device_hw_addr=HW_ADDR) # Test the BACNet traffic for a matching Object ID and HW address diff --git a/testing/unit/report/report_test.py b/testing/unit/report/report_test.py index f92666b2c..c67d81b1e 100644 --- a/testing/unit/report/report_test.py +++ b/testing/unit/report/report_test.py @@ -16,6 +16,7 @@ from testreport import TestReport import os import json +import shutil MODULE = 'report' @@ -30,6 +31,10 @@ class ReportTest(unittest.TestCase): @classmethod def setUpClass(cls): + # Delete old files + if os.path.exists(OUTPUT_DIR) and os.path.isdir(OUTPUT_DIR): + shutil.rmtree(OUTPUT_DIR) + # Create the output directories and ignore errors if it already exists os.makedirs(OUTPUT_DIR, exist_ok=True) @@ -59,6 +64,47 @@ def report_compliant_test(self): def report_noncompliant_test(self): self.create_report(os.path.join(TEST_FILES_DIR, 'report_noncompliant.json')) + # Generate formatted reports for each report generated from + # the test containers. + # Not a unit test but can't run from within the test module container and must + # be done through the venv. Useful for doing visual inspections + # of report formatting changes without having to re-run a new device test. + def report_formatting(self): + test_modules = ['conn','dns','ntp','protocol','services','tls'] + unit_tests = os.listdir(UNIT_TEST_DIR) + for test in unit_tests: + if test in test_modules: + output_dir = os.path.join(UNIT_TEST_DIR,test,'output') + if os.path.isdir(output_dir): + output_files = os.listdir(output_dir) + for file in output_files: + if file.endswith('.html'): + + # Read the generated report and add formatting + report_out_path = os.path.join(output_dir,file) + with open(report_out_path, 'r', encoding='utf-8') as f: + report_out = f.read() + formatted_report = self.add_formatting(report_out) + + # Write back the new formatted_report value + out_report_dir = os.path.join(OUTPUT_DIR, test) + os.makedirs(out_report_dir, exist_ok=True) + + with open(os.path.join( + out_report_dir,file), 'w', + encoding='utf-8') as f: + f.write(formatted_report) + + def add_formatting(self, body): + return f''' + + + {TestReport().generate_head()} + + {body} + + /dev/null 2>&1 - -echo "Root dir: $PWD" - -# Add the framework sources -PYTHONPATH="$PWD/framework/python/src:$PWD/framework/python/src/common" - -# Add the test module sources -PYTHONPATH="$PYTHONPATH:$PWD/modules/test/base/python/src" -PYTHONPATH="$PYTHONPATH:$PWD/modules/test/conn/python/src" -PYTHONPATH="$PYTHONPATH:$PWD/modules/test/tls/python/src" -PYTHONPATH="$PYTHONPATH:$PWD/modules/test/dns/python/src" -PYTHONPATH="$PYTHONPATH:$PWD/modules/test/services/python/src" -PYTHONPATH="$PYTHONPATH:$PWD/modules/test/ntp/python/src" -PYTHONPATH="$PYTHONPATH:$PWD/modules/test/protocol/python/src" - - -# Set the python path with all sources -export PYTHONPATH - -# Run the DHCP Unit tests -python3 -u $PWD/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py -python3 -u $PWD/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py - -# Run the Conn Module Unit Tests -python3 -u $PWD/testing/unit/conn/conn_module_test.py - -# Run the TLS Module Unit Tests -python3 -u $PWD/testing/unit/tls/tls_module_test.py - -# Run the DNS Module Unit Tests -python3 -u $PWD/testing/unit/dns/dns_module_test.py - -# Run the NMAP Module Unit Tests -python3 -u $PWD/testing/unit/services/services_module_test.py - -# Run the NTP Module Unit Tests -python3 -u $PWD/testing/unit/ntp/ntp_module_test.py - -# Run the Report Unit Tests -python3 -u $PWD/testing/unit/report/report_test.py - -# Run the RiskProfile Unit Tests -python3 -u $PWD/testing/unit/risk_profile/risk_profile_test.py - -# Run the RiskProfile Unit Tests -python3 -u $PWD/testing/unit/protocol/protocol_module_test.py - -popd >/dev/null 2>&1 diff --git a/testing/unit/services/output/services.log b/testing/unit/services/output/services.log deleted file mode 100644 index 7df3f745b..000000000 --- a/testing/unit/services/output/services.log +++ /dev/null @@ -1,6 +0,0 @@ -Jun 17 09:23:01 test_services INFO Module report generated at: testing/unit/services/output/services_report.html -Jun 17 09:23:01 test_services INFO Module report generated at: testing/unit/services/output/services_report.html -Jun 17 09:23:01 test_services INFO Module report generated at: testing/unit/services/output/services_report.html -Jun 17 09:32:48 test_services INFO Module report generated at: testing/unit/services/output/services_report.html -Jun 17 09:32:48 test_services INFO Module report generated at: testing/unit/services/output/services_report.html -Jun 17 09:32:48 test_services INFO Module report generated at: testing/unit/services/output/services_report.html diff --git a/testing/unit/services/services_module_test.py b/testing/unit/services/services_module_test.py index 30c4928bf..a8c60262b 100644 --- a/testing/unit/services/services_module_test.py +++ b/testing/unit/services/services_module_test.py @@ -16,7 +16,7 @@ import unittest import os import shutil -from testreport import TestReport +# from testreport import TestReport MODULE = 'services' @@ -29,8 +29,6 @@ LOCAL_REPORT = os.path.join(REPORTS_DIR, 'services_report_local.html') LOCAL_REPORT_ALL_CLOSED = os.path.join(REPORTS_DIR, 'services_report_all_closed_local.html') -CONF_FILE = 'modules/test/' + MODULE + '/conf/module_config.json' - class ServicesTest(unittest.TestCase): """Contains and runs all the unit tests concerning DNS behaviors""" @@ -51,7 +49,6 @@ def services_module_ports_open_report_test(self): services_module = ServicesModule(module=MODULE, log_dir=OUTPUT_DIR, - conf_file=CONF_FILE, results_dir=OUTPUT_DIR, run=False, nmap_scan_results_path=OUTPUT_DIR) @@ -61,13 +58,6 @@ def services_module_ports_open_report_test(self): # Read the generated report with open(report_out_path, 'r', encoding='utf-8') as file: report_out = file.read() - formatted_report = self.add_formatting(report_out) - - # Write back the new formatted_report value - out_report_path = os.path.join( - OUTPUT_DIR, 'services_report_ports_open.html') - with open(out_report_path, 'w', encoding='utf-8') as file: - file.write(formatted_report) # Read the local good report with open(LOCAL_REPORT, 'r', encoding='utf-8') as file: @@ -85,7 +75,6 @@ def services_module_report_all_closed_test(self): services_module = ServicesModule(module=MODULE, log_dir=OUTPUT_DIR, - conf_file=CONF_FILE, results_dir=OUTPUT_DIR, run=False, nmap_scan_results_path=OUTPUT_DIR) @@ -95,13 +84,6 @@ def services_module_report_all_closed_test(self): # Read the generated report with open(report_out_path, 'r', encoding='utf-8') as file: report_out = file.read() - formatted_report = self.add_formatting(report_out) - - # Write back the new formatted_report value - out_report_path = os.path.join( - OUTPUT_DIR, 'services_report_all_closed.html') - with open(out_report_path, 'w', encoding='utf-8') as file: - file.write(formatted_report) # Read the local good report with open(LOCAL_REPORT_ALL_CLOSED, 'r', encoding='utf-8') as file: @@ -109,17 +91,6 @@ def services_module_report_all_closed_test(self): self.assertEqual(report_out, report_local) - def add_formatting(self, body): - return f''' - - - {TestReport().generate_head()} - - {body} - - Date: Fri, 11 Oct 2024 13:56:34 +0100 Subject: [PATCH 02/12] Merge release v2.0 into main (#876) --- .github/workflows/package.yml | 2 +- .github/workflows/testing.yml | 45 +- README.md | 125 +- cmd/build | 9 +- cmd/install | 6 +- cmd/prune | 11 +- docs/README.md | 30 +- docs/configure_device.md | 31 - docs/dev/README.md | 38 +- docs/dev/code_quality.md | 16 - docs/get_started.md | 158 +- docs/network/README.md | 41 +- docs/network/add_new_service.md | 79 +- docs/network/addresses.md | 27 +- docs/network/identify_interfaces.md | 20 - docs/roadmap.pdf | Bin 515039 -> 817448 bytes docs/test/README.md | 6 +- docs/test/modules.md | 34 +- docs/test/statuses.md | 47 +- docs/ui/accessibility.md | 12 + docs/ui/device_icon.png | Bin 933 -> 0 bytes docs/ui/getstarted--2dn8vrzsspe.png | Bin 0 -> 36356 bytes docs/ui/getstarted--3d9k3si3ul1.png | Bin 0 -> 10189 bytes docs/ui/getstarted--7cfvdpdnc5o.png | Bin 0 -> 17881 bytes docs/ui/getstarted--m4si1otdu5d.png | Bin 0 -> 13909 bytes docs/ui/getstarted--q5uw26tfod.png | Bin 0 -> 13929 bytes docs/ui/getstarted--w09wecsry3.png | Bin 0 -> 14001 bytes docs/ui/history_icon.png | Bin 538 -> 0 bytes docs/ui/progress_icon.png | Bin 989 -> 0 bytes docs/ui/settings_icon.png | Bin 539 -> 0 bytes docs/ui/settings_menu.png | Bin 37230 -> 0 bytes docs/ui/test_name.png | Bin 10221 -> 0 bytes docs/virtual_machine.md | 51 +- framework/python/src/api/api.py | 270 +- framework/python/src/common/device.py | 19 +- framework/python/src/common/docker_util.py | 35 + framework/python/src/common/mqtt.py | 21 +- framework/python/src/common/risk_profile.py | 471 +- framework/python/src/common/statuses.py | 36 + framework/python/src/common/testreport.py | 1012 +- framework/python/src/common/util.py | 9 +- .../python/src/core/docker/docker_module.py | 163 + .../src/core/docker/network_docker_module.py | 98 + .../src/core/docker/test_docker_module.py | 135 + .../python/src/{common => core}/session.py | 210 +- framework/python/src/core/tasks.py | 78 + framework/python/src/core/test_runner.py | 4 +- framework/python/src/core/testrun.py | 125 +- framework/python/src/net_orc/ip_control.py | 19 + .../src/net_orc/network_orchestrator.py | 274 +- .../python/src/net_orc/network_validator.py | 2 +- framework/python/src/test_orc/test_case.py | 35 +- .../python/src/test_orc/test_orchestrator.py | 423 +- framework/python/src/test_orc/test_pack.py | 42 + framework/requirements.txt | 3 + local/system.json.example | 7 +- make/DEBIAN/control | 2 +- .../faux-dev/bin/start_network_service | 2 +- modules/devices/faux-dev/faux-dev.Dockerfile | 9 +- modules/devices/faux-dev/python/src/logger.py | 61 - modules/devices/faux-dev/python/src/util.py | 48 - modules/network/base/base.Dockerfile | 11 +- modules/network/base/bin/start_module | 20 +- .../python/src/grpc_server/start_server.py | 1 - modules/network/dhcp-1/dhcp-1.Dockerfile | 4 +- modules/network/dhcp-2/dhcp-2.Dockerfile | 4 +- modules/network/dns/dns.Dockerfile | 4 +- modules/network/gateway/gateway.Dockerfile | 4 +- .../network/host/bin/start_network_service | 22 + modules/network/host/conf/module_config.json | 24 + modules/network/host/host.Dockerfile | 34 + .../python/src/grpc_server/network_service.py | 120 + .../python/src/grpc_server/proto/grpc.proto | 37 + .../python/src/grpc_server/start_server.py | 50 + modules/network/ntp/ntp.Dockerfile | 4 +- modules/network/ntp/python/src/ntp_server.py | 2 +- modules/network/radius/radius.Dockerfile | 10 +- modules/network/template/template.Dockerfile | 4 +- modules/test/base/base.Dockerfile | 35 +- modules/test/base/bin/setup | 2 +- .../base/python/src/grpc/proto/host/client.py | 63 + modules/test/base/python/src/logger.py | 61 - modules/test/base/python/src/test_module.py | 35 +- modules/test/base/python/src/util.py | 47 - modules/test/baseline/baseline.Dockerfile | 4 +- modules/test/baseline/conf/module_config.json | 9 +- modules/test/conn/conf/module_config.json | 37 +- modules/test/conn/conn.Dockerfile | 6 +- .../test/conn/python/src/connection_module.py | 171 +- modules/test/dns/conf/module_config.json | 5 +- modules/test/dns/dns.Dockerfile | 6 +- modules/test/dns/python/src/dns_module.py | 37 +- modules/test/ntp/conf/module_config.json | 4 +- modules/test/ntp/ntp.Dockerfile | 20 +- modules/test/ntp/python/src/ntp_module.py | 8 +- modules/test/protocol/conf/module_config.json | 7 +- modules/test/protocol/protocol.Dockerfile | 6 +- modules/test/services/conf/module_config.json | 11 - .../services/python/src/services_module.py | 2 +- modules/test/services/services.Dockerfile | 6 +- .../test/tls/bin/get_client_hello_packets.sh | 12 +- .../test/tls/bin/get_handshake_complete.sh | 72 +- modules/test/tls/conf/module_config.json | 32 +- modules/test/tls/python/requirements.txt | 4 +- modules/test/tls/python/src/tls_module.py | 111 +- modules/test/tls/python/src/tls_util.py | 80 +- modules/test/tls/tls.Dockerfile | 9 +- modules/ui/angular.json | 4 +- modules/ui/build.Dockerfile | 3 +- modules/ui/package-lock.json | 10555 ++++++++-------- modules/ui/package.json | 52 +- modules/ui/src/app/app.component.html | 240 +- modules/ui/src/app/app.component.scss | 10 +- modules/ui/src/app/app.component.spec.ts | 71 +- modules/ui/src/app/app.component.ts | 64 +- modules/ui/src/app/app.module.ts | 8 +- modules/ui/src/app/app.store.spec.ts | 41 +- modules/ui/src/app/app.store.ts | 48 + .../components/callout/callout.component.html | 11 + .../components/callout/callout.component.scss | 40 +- .../callout/callout.component.spec.ts | 26 +- .../components/callout/callout.component.ts | 28 +- .../components/component-with-announcement.ts | 35 + .../device-item/device-item.component.html | 61 +- .../device-item/device-item.component.scss | 70 +- .../device-item/device-item.component.spec.ts | 56 +- .../device-item/device-item.component.ts | 25 +- .../download-report-zip.component.spec.ts | 18 +- .../download-report-zip.component.ts | 14 +- .../download-report.component.html | 1 + .../download-report.component.scss | 1 + .../download-report.component.ts | 4 + .../download-zip-modal.component.html | 85 +- .../download-zip-modal.component.scss | 87 + .../download-zip-modal.component.ts | 10 +- .../dynamic-form/dynamic-form.component.html | 268 + .../dynamic-form/dynamic-form.component.scss | 67 + .../dynamic-form.component.spec.ts | 235 + .../dynamic-form/dynamic-form.component.ts | 171 + .../program-type-con.component.spec.ts | 46 + .../program-type-icon.component.ts | 39 + .../report-action/report-action.component.ts | 3 + .../simple-dialog.component.html | 2 + .../simple-dialog.component.scss | 3 +- .../simple-dialog.component.spec.ts | 2 + .../simple-dialog/simple-dialog.component.ts | 14 +- .../components/spinner/spinner.component.scss | 2 +- .../stepper/stepper-test.component.html | 39 + .../components/stepper/stepper.component.html | 56 + .../components/stepper/stepper.component.scss | 91 + .../stepper/stepper.component.spec.ts | 101 + .../components/stepper/stepper.component.ts | 72 + .../testing-complete.component.spec.ts | 91 + .../testing-complete.component.ts | 99 + .../consent-dialog.component.html | 24 +- .../consent-dialog.component.scss | 11 + .../consent-dialog.component.spec.ts | 89 +- .../consent-dialog.component.ts | 10 +- .../components/version/version.component.html | 2 +- .../version/version.component.spec.ts | 32 +- .../components/version/version.component.ts | 34 +- .../app/guards/can-deactivate.guard.spec.ts | 31 + .../ui/src/app/guards/can-deactivate.guard.ts | 38 + modules/ui/src/app/mocks/device.mock.ts | 77 +- modules/ui/src/app/mocks/profile.mock.ts | 8 +- modules/ui/src/app/mocks/reports.mock.ts | 28 + modules/ui/src/app/mocks/testrun.mock.ts | 36 +- modules/ui/src/app/model/callout-type.ts | 1 + modules/ui/src/app/model/device.ts | 29 + modules/ui/src/app/model/profile.ts | 24 +- modules/ui/src/app/model/program-type.ts | 19 + modules/ui/src/app/model/question.ts | 38 + modules/ui/src/app/model/setting.ts | 1 + modules/ui/src/app/model/testrun-status.ts | 5 + modules/ui/src/app/model/version.ts | 1 - .../certificates/certificate.validator.ts | 1 - .../certificates/certificates.component.html | 9 - .../certificates.component.spec.ts | 18 +- .../certificates/certificates.component.ts | 2 +- .../device-form/device-form.component.html | 130 - .../device-form/device-form.component.scss | 76 - .../device-form/device-form.component.spec.ts | 459 - .../device-form/device-form.component.ts | 211 - .../device-form/device.validators.ts | 10 + .../device-qualification-from.component.html | 347 + .../device-qualification-from.component.scss | 359 + ...evice-qualification-from.component.spec.ts | 760 ++ .../device-qualification-from.component.ts | 574 + .../pages/devices/devices-routing.module.ts | 9 +- .../app/pages/devices/devices.component.html | 7 +- .../app/pages/devices/devices.component.scss | 3 + .../pages/devices/devices.component.spec.ts | 163 +- .../app/pages/devices/devices.component.ts | 242 +- .../src/app/pages/devices/devices.module.ts | 3 +- .../ui/src/app/pages/devices/devices.store.ts | 23 +- .../delete-report.component.html | 4 +- .../filter-dialog.component.scss | 2 +- .../app/pages/reports/reports.component.html | 30 +- .../app/pages/reports/reports.component.scss | 6 +- .../pages/reports/reports.component.spec.ts | 20 +- .../app/pages/reports/reports.component.ts | 10 +- .../app/pages/reports/reports.store.spec.ts | 1 + .../ui/src/app/pages/reports/reports.store.ts | 2 + .../src/app/pages/reports/reportscomponent.ts | 173 - .../success-dialog.component.html | 45 + .../success-dialog.component.scss | 73 + .../success-dialog.component.spec.ts | 78 + .../success-dialog.component.ts | 65 + .../profile-form/profile-form.component.html | 256 +- .../profile-form/profile-form.component.scss | 53 +- .../profile-form.component.spec.ts | 171 +- .../profile-form/profile-form.component.ts | 74 +- .../profile-item/profile-item.component.html | 12 +- .../profile-item/profile-item.component.ts | 16 +- .../risk-assessment.component.scss | 2 + .../risk-assessment.component.spec.ts | 49 +- .../risk-assessment.component.ts | 108 +- .../risk-assessment.store.spec.ts | 31 +- .../risk-assessment/risk-assessment.store.ts | 46 +- .../pages/settings/settings.component.scss | 7 + .../app/pages/settings/settings.component.ts | 6 +- .../download-options.component.scss | 8 +- .../download-options.component.ts | 3 + .../testrun-initiate-form.component.html | 1 + .../testrun-initiate-form.component.spec.ts | 21 +- .../testrun-initiate-form.component.ts | 18 +- .../testrun-status-card.component.html | 8 +- .../testrun-status-card.component.scss | 4 +- .../testrun-status-card.component.spec.ts | 11 +- .../testrun-status-card.component.ts | 13 +- .../testrun-table.component.spec.ts | 1 + .../app/pages/testrun/testrun.component.html | 26 +- .../app/pages/testrun/testrun.component.scss | 33 +- .../pages/testrun/testrun.component.spec.ts | 33 +- .../app/pages/testrun/testrun.component.ts | 13 +- .../src/app/pages/testrun/testrun.module.ts | 2 + .../app/pages/testrun/testrun.store.spec.ts | 3 + .../ui/src/app/pages/testrun/testrun.store.ts | 3 + .../src/app/services/notification.service.ts | 17 +- .../src/app/services/test-run.service.spec.ts | 32 +- .../ui/src/app/services/test-run.service.ts | 43 +- modules/ui/src/app/store/actions.ts | 15 + modules/ui/src/app/store/effects.spec.ts | 26 +- modules/ui/src/app/store/effects.ts | 67 +- modules/ui/src/app/store/reducers.spec.ts | 44 + modules/ui/src/app/store/reducers.ts | 18 + modules/ui/src/app/store/selectors.spec.ts | 21 + modules/ui/src/app/store/selectors.ts | 14 + modules/ui/src/app/store/state.ts | 6 + .../src/assets/icons/create_device_header.svg | 64 + modules/ui/src/assets/icons/pilot.svg | 13 + modules/ui/src/assets/icons/qualification.svg | 6 + modules/ui/src/index.html | 18 +- modules/ui/src/styles.scss | 187 +- modules/ui/src/theming/colors.scss | 1 + modules/ui/src/theming/theme.scss | 21 +- modules/ui/src/theming/variables.scss | 3 + modules/ws/conf/mosquitto.conf | 2 +- resources/devices/device_profile.json | 420 + resources/report/header_macros.jinja | 43 + resources/report/pilot-icon.png | Bin 0 -> 536 bytes resources/report/qualification-icon.png | Bin 0 -> 428 bytes resources/report/risk_report_styles.css | 211 + resources/report/risk_report_template.html | 75 + resources/report/test_report_styles.css | 633 + resources/report/test_report_template.html | 241 + resources/risk_assessment.json | 200 - resources/test_packs/pilot.json | 169 + resources/test_packs/qualification.json | 177 + testing/api/certificates/WR2.pem | 29 + testing/api/certificates/crt.pem | 31 + testing/api/certificates/invalid.pem | 1 + .../certificates/invalidname1234567891234.pem | 31 + .../api/devices/device_1/device_config.json | 54 + .../api/devices/device_2/device_config.json | 54 + testing/api/profiles/draft_profile.json | 35 + testing/api/profiles/new_profile.json | 54 - testing/api/profiles/new_profile_2.json | 56 - testing/api/profiles/updated_profile.json | 57 - testing/api/profiles/valid_profile.json | 39 + testing/api/reports/report.json | 134 + testing/api/reports/report.pdf | Bin 0 -> 29659 bytes testing/api/{ => sys_config}/system.json | 0 testing/api/sys_config/updated_system.json | 7 + testing/api/test_api | 5 +- testing/api/test_api.py | 3762 ++++-- testing/baseline/test_baseline | 9 +- testing/docker/ci_baseline/Dockerfile | 2 +- testing/unit/conn/conn_module_test.py | 8 +- testing/unit/dns/dns_module_test.py | 8 +- .../unit/dns/reports/dns_report_local.html | 98 +- .../dns/reports/dns_report_local_no_dns.html | 2 +- testing/unit/framework/session_test.py | 2 +- testing/unit/ntp/ntp_module_test.py | 9 +- .../unit/ntp/reports/ntp_report_local.html | 2 +- .../ntp/reports/ntp_report_local_no_ntp.html | 2 +- testing/unit/protocol/protocol_module_test.py | 8 +- testing/unit/report/report_test.py | 92 +- .../profiles/risk_profile_valid_high.json | 42 +- .../profiles/risk_profile_valid_limited.json | 33 +- .../unit/risk_profile/risk_profile_test.py | 16 +- testing/unit/run.sh | 2 +- testing/unit/run_report_test.sh | 67 + testing/unit/run_test_module.sh | 80 + .../services_report_all_closed_local.html | 2 +- .../reports/services_report_local.html | 2 +- testing/unit/services/services_module_test.py | 9 +- testing/unit/tls/tls_module_test.py | 49 +- 308 files changed, 19563 insertions(+), 12329 deletions(-) delete mode 100644 docs/configure_device.md delete mode 100644 docs/dev/code_quality.md delete mode 100644 docs/network/identify_interfaces.md create mode 100644 docs/ui/accessibility.md delete mode 100644 docs/ui/device_icon.png create mode 100644 docs/ui/getstarted--2dn8vrzsspe.png create mode 100644 docs/ui/getstarted--3d9k3si3ul1.png create mode 100644 docs/ui/getstarted--7cfvdpdnc5o.png create mode 100644 docs/ui/getstarted--m4si1otdu5d.png create mode 100644 docs/ui/getstarted--q5uw26tfod.png create mode 100644 docs/ui/getstarted--w09wecsry3.png delete mode 100644 docs/ui/history_icon.png delete mode 100644 docs/ui/progress_icon.png delete mode 100644 docs/ui/settings_icon.png delete mode 100644 docs/ui/settings_menu.png delete mode 100644 docs/ui/test_name.png create mode 100644 framework/python/src/common/docker_util.py create mode 100644 framework/python/src/common/statuses.py create mode 100644 framework/python/src/core/docker/docker_module.py create mode 100644 framework/python/src/core/docker/network_docker_module.py create mode 100644 framework/python/src/core/docker/test_docker_module.py rename framework/python/src/{common => core}/session.py (76%) create mode 100644 framework/python/src/core/tasks.py create mode 100644 framework/python/src/test_orc/test_pack.py delete mode 100644 modules/devices/faux-dev/python/src/logger.py delete mode 100644 modules/devices/faux-dev/python/src/util.py create mode 100644 modules/network/host/bin/start_network_service create mode 100644 modules/network/host/conf/module_config.json create mode 100644 modules/network/host/host.Dockerfile create mode 100644 modules/network/host/python/src/grpc_server/network_service.py create mode 100644 modules/network/host/python/src/grpc_server/proto/grpc.proto create mode 100644 modules/network/host/python/src/grpc_server/start_server.py create mode 100644 modules/test/base/python/src/grpc/proto/host/client.py delete mode 100644 modules/test/base/python/src/logger.py delete mode 100644 modules/test/base/python/src/util.py create mode 100644 modules/ui/src/app/components/component-with-announcement.ts create mode 100644 modules/ui/src/app/components/dynamic-form/dynamic-form.component.html create mode 100644 modules/ui/src/app/components/dynamic-form/dynamic-form.component.scss create mode 100644 modules/ui/src/app/components/dynamic-form/dynamic-form.component.spec.ts create mode 100644 modules/ui/src/app/components/dynamic-form/dynamic-form.component.ts create mode 100644 modules/ui/src/app/components/program-type-icon/program-type-con.component.spec.ts create mode 100644 modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts create mode 100644 modules/ui/src/app/components/stepper/stepper-test.component.html create mode 100644 modules/ui/src/app/components/stepper/stepper.component.html create mode 100644 modules/ui/src/app/components/stepper/stepper.component.scss create mode 100644 modules/ui/src/app/components/stepper/stepper.component.spec.ts create mode 100644 modules/ui/src/app/components/stepper/stepper.component.ts create mode 100644 modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts create mode 100644 modules/ui/src/app/components/testing-complete/testing-complete.component.ts create mode 100644 modules/ui/src/app/guards/can-deactivate.guard.spec.ts create mode 100644 modules/ui/src/app/guards/can-deactivate.guard.ts create mode 100644 modules/ui/src/app/model/program-type.ts create mode 100644 modules/ui/src/app/model/question.ts delete mode 100644 modules/ui/src/app/pages/devices/components/device-form/device-form.component.html delete mode 100644 modules/ui/src/app/pages/devices/components/device-form/device-form.component.scss delete mode 100644 modules/ui/src/app/pages/devices/components/device-form/device-form.component.spec.ts delete mode 100644 modules/ui/src/app/pages/devices/components/device-form/device-form.component.ts create mode 100644 modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.html create mode 100644 modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss create mode 100644 modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts create mode 100644 modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts delete mode 100644 modules/ui/src/app/pages/reports/reportscomponent.ts create mode 100644 modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.html create mode 100644 modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.scss create mode 100644 modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.spec.ts create mode 100644 modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.ts create mode 100644 modules/ui/src/assets/icons/create_device_header.svg create mode 100644 modules/ui/src/assets/icons/pilot.svg create mode 100644 modules/ui/src/assets/icons/qualification.svg create mode 100644 resources/devices/device_profile.json create mode 100644 resources/report/header_macros.jinja create mode 100644 resources/report/pilot-icon.png create mode 100644 resources/report/qualification-icon.png create mode 100644 resources/report/risk_report_styles.css create mode 100644 resources/report/risk_report_template.html create mode 100644 resources/report/test_report_styles.css create mode 100644 resources/report/test_report_template.html create mode 100644 resources/test_packs/pilot.json create mode 100644 resources/test_packs/qualification.json create mode 100644 testing/api/certificates/WR2.pem create mode 100644 testing/api/certificates/crt.pem create mode 100644 testing/api/certificates/invalid.pem create mode 100644 testing/api/certificates/invalidname1234567891234.pem create mode 100644 testing/api/devices/device_1/device_config.json create mode 100644 testing/api/devices/device_2/device_config.json create mode 100644 testing/api/profiles/draft_profile.json delete mode 100644 testing/api/profiles/new_profile.json delete mode 100644 testing/api/profiles/new_profile_2.json delete mode 100644 testing/api/profiles/updated_profile.json create mode 100644 testing/api/profiles/valid_profile.json create mode 100644 testing/api/reports/report.json create mode 100644 testing/api/reports/report.pdf rename testing/api/{ => sys_config}/system.json (100%) create mode 100644 testing/api/sys_config/updated_system.json mode change 100644 => 100755 testing/unit/run.sh create mode 100644 testing/unit/run_report_test.sh create mode 100644 testing/unit/run_test_module.sh diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 8c4b5bcbe..99bfa4e96 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -17,7 +17,7 @@ jobs: permissions: {} name: Package runs-on: ubuntu-22.04 - timeout-minutes: 5 + timeout-minutes: 10 steps: - name: Checkout source uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index d6deb1ab0..2aaa55b25 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -10,7 +10,7 @@ jobs: testrun_baseline: permissions: {} name: Baseline - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 20 steps: - name: Checkout source @@ -29,7 +29,7 @@ jobs: testrun_api: permissions: {} name: API - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 60 steps: - name: Checkout source @@ -58,7 +58,7 @@ jobs: testrun_unit: permissions: {} name: Unit - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 15 steps: - name: Checkout source @@ -73,9 +73,40 @@ jobs: shell: bash {0} run: cmd/build timeout-minutes: 10 - - name: Run tests + - name: Run tests for conn module + shell: bash {0} + run: bash testing/unit/run_test_module.sh conn captures ethtool output + - name: Run tests for dns module + shell: bash {0} + run: bash testing/unit/run_test_module.sh dns captures reports output + - name: Run tests for ntp module + shell: bash {0} + run: bash testing/unit/run_test_module.sh ntp captures reports output + - name: Run tests for protocol module + shell: bash {0} + run: bash testing/unit/run_test_module.sh protocol captures output + - name: Run tests for services module shell: bash {0} - run: bash testing/unit/run.sh + run: bash testing/unit/run_test_module.sh services reports results output + - name: Run tests for tls module + shell: bash {0} + run: bash testing/unit/run_test_module.sh tls captures certAuth certs reports root_certs output + - name: Run tests for risk profiles + shell: bash {0} + run: bash testing/unit/run_report_test.sh testing/unit/risk_profile/risk_profile_test.py + - name: Run tests for reports + shell: bash {0} + run: bash testing/unit/run_report_test.sh testing/unit/report/report_test.py + - name: Archive HTML reports for modules + if: ${{ always() }} + run: sudo tar --exclude-vcs -czf html_reports.tgz testing/unit/report/output/ + - name: Upload HTML reports + uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 + if: ${{ always() }} + with: + if-no-files-found: error + name: html-reports_${{ github.run_id }} + path: html_reports.tgz pylint: permissions: {} @@ -98,7 +129,7 @@ jobs: - name: Install Node uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 with: - node-version: 18.18.0 + node-version: 18.19.0 - name: Install Chromium Browser run: sudo apt install chromium-browser - name: Install dependencies @@ -121,7 +152,7 @@ jobs: - name: Install Node uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 with: - node-version: 18.18.0 + node-version: 18.19.0 - name: Install dependencies run: npm install && npm ci working-directory: ./modules/ui diff --git a/README.md b/README.md index 23fd843ca..ce9602637 100644 --- a/README.md +++ b/README.md @@ -4,84 +4,93 @@ [![CodeQL](https://github.com/google/testrun/actions/workflows/github-code-scanning/codeql/badge.svg?branch=main)](https://github.com/google/testrun/actions/workflows/github-code-scanning/codeql) [![Testrun test suite](https://github.com/google/testrun/actions/workflows/testing.yml/badge.svg?branch=main&event=push)](https://github.com/google/testrun/actions/workflows/testing.yml) -## Introduction :wave: -Testrun automates specific test cases to verify network and security functionality in IoT devices. It is an open source tool which allows manufacturers of IP capable devices to test their devices for the purposes of Device Qualification within the BOS program. +# Introduction :wave: -## Motivation :bulb: -Without tools like Testrun, test labs and engineers may need to maintain a large and complex network coupled with dynamic configuration files and constant software updates. The major issues which can and should be solved are: - 1) The complexity of managing a testing network - 2) The time required to perform testing of network functionality - 3) The accuracy and consistency of testing network functionality +Testrun automates specific test cases to verify network and security functionality in IoT devices. It's an open-source tool that manufacturers use to test their IP-capable devices for the purpose of device qualification within Google's Building Operating System (BOS) program. -## How it works :triangular_ruler: -Testrun creates an isolated and controlled network environment on a linux machine. This removes the necessity for complex hardware, advanced knowledge and networking experience whilst enabling test engineers to validate device behaviour against Google’s Building Operating System requirements. +# Motivation :bulb: -Two modes are supported by Testrun: +Test labs and engineers often need to maintain a large and complex network coupled with dynamic configuration files and constant software updates. Testrun helps address major issues like: -
- - Automated testing - +- The complexity of managing a testing network +- The time required to perform testing of network functionality +- The accuracy and consistency of testing network functionality -Once the device has become operational (steady state), automated testing of the DUT (device under test) will begin. Containerized test modules will then execute against the device, one module at a time. Once all test modules have been executed, a report will be produced - presenting the results. -
+# How it works :triangular_ruler: -
+Testrun creates an isolated and controlled network environment on a Linux machine. This removes the necessity for complex hardware, advanced knowledge, and networking experience while enabling test engineers to validate device behavior against Google's BOS requirements. - - Lab network - +Testrun supports two modes: automated testing and lab network. -When manual testing or configuration changes are required, Testrun will provide the network and some tools to assist an engineer performing the additional testing. This reduces the need to maintain a separate but identical lab network. Testrun will take care of packet captures and logs for each network service for further debugging. +## Automated testing -
+Automated testing of the device under test (DUT) begins once the device is operational (steady state). Containerized test modules execute against the device one module at a time. Testrun produces a report with the results after all modules are executed. -## Minimum requirements :computer: -### Hardware - - PC running Ubuntu LTS 20.04, 22.04 or 24.04 (laptop or desktop) - - 2x USB ethernet adapter (One may be built in ethernet) - - Internet connection -### Software -- Docker - installation guide: [https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository) -### Device under test (DUT) - - DHCP client - The device must be able to obtain an IP address via DHCP +## Lab network -## Get started ▶️ -Once you have met the hardware and software requirements, you can get started with Testrun by following the [Get started guide](docs/get_started.md). Further docs are available in the [docs directory](docs) +Testrun provides the network and assistive tools for engineers when manual testing or configuration changes are required, reducing the need to maintain a separate but identical lab network. Testrun handles packet captures and logs for each network service for further debugging. -## Roadmap :chart_with_upwards_trend: -Testrun will constantly evolve to further support end-users by automating device network behaviour against industry standards. For further information on upcoming features, check out the [Roadmap](docs/roadmap.pdf). +# Minimum requirements :computer: -## Accessibility :busts_in_silhouette: -We are proud of our tool and strive to provide an enjoyable experience for all of our users. Testrun goes through rigorous accessibility testing at each release. You can read more about [Google and Accessibility here](https://www.google.co.uk/accessibility). You are welcome to submit a new issue and provide feedback on our implementations. To find out how Testrun implements accessibility features, you can view a [short video here](docs/ui/accessibility.mp4). +## Hardware -## Issue reporting :triangular_flag_on_post: -If the application has come across a problem at any point during setup or use, please raise an issue under the [issues tab](https://github.com/google/testrun/issues). Issue templates exist for both bug reports and feature requests. If neither of these are appropriate for your issue, raise a blank issue instead. +- PC running Ubuntu LTS 20.04, 22.04, or 24.04 (laptop or desktop) +- 2x USB Ethernet adapter (one may be built-in Ethernet) +- Internet connection -## Contributing :keyboard: -The contributing requirements can be found in [CONTRIBUTING.md](CONTRIBUTING.md). In short, checkout the [Google CLA](https://cla.developers.google.com/) site to get started. After that, check out our [developer documentation](docs/dev/README.md). +## Software -## FAQ :raising_hand: -1) I have an issue whilst installing/upgrading Testrun, what do I do? +Testrun requires Docker. Refer to the [installation guide](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository) for more information. - Sometimes, issues may arise when installing or upgrading Testrun - this may happen due to one of many reasons due to the nature of the application. However, most of the time, it can be resolved by following a full Testrun re-install by using these commands: - - ```sudo docker system prune -a``` - - ```sudo apt install ./testrun-*.deb``` +## Device under test (DUT) -2) What device networking functionality is validated by Testrun? +The DUT must be able to obtain an IP address via DHCP. - Best practices and requirements for IoT devices are constantly changing due to technological advances and discovery of vulnerabilities. - The current expectations for IoT devices on Google deployments can be found in the [Application Security Requirements for IoT Devices](https://partner-security.withgoogle.com/docs/iot_requirements). - Testrun aims to automate as much of the Application Security Requirements as possible. +# Get started :arrow_forward: -3) What services are provided on the virtual network? +Once you meet the hardware and software requirements, follow the Testrun [Get started guide](/docs/get_started.md). Additional guidance is available in the [docs directory](/docs). - The following are network services that are containerized and accessible to the device under test though are likely to change over time: - - DHCP in failover configuration with internet connectivity - - IPv6 SLAAC - - DNS - - NTPv4 +# Roadmap :chart_with_upwards_trend: -4) Can I run Testrun on a virtual machine? +Testrun continually evolves to further support end users by automating device network behavior against industry standards. For information on upcoming features, check out the [Roadmap](/docs/roadmap.pdf). - Testrun can be virtualized if the 2x ethernet adapters are passed through to a VirtualBox VM as a USB device rather than managed network adapters. You can view the guide to working on a [virtual machine here](docs/virtual_machine.md). +# Accessibility :busts_in_silhouette: + +We're proud of our tool and strive to provide an enjoyable experience for everyone. Testrun goes through rigorous accessibility testing at each release. Download the [Testrun: Accessible features](https://github.com/google/testrun/raw/refs/heads/main/docs/ui/accessibility.mp4) video to learn more.You're welcome to [submit a new issue](https://github.com/google/testrun/issues) and provide feedback on our implementations. To learn more about Google's [Belonging initiative](https://www.google.co.uk/accessibility) and their approach to accessibility, visit their site. + +# Issue reporting :triangular_flag_on_post: + +If you encounter a problem during setup or use, raise an issue under the [Issues tab](https://github.com/google/testrun/issues). Issue templates exist for both bug reports and feature requests. If neither of these apply, raise a blank issue instead. + +# Contributing :keyboard: + +We strongly encourage contributions from the community. Review the requirements on the ["How to Contribute" page](CONTRIBUTING.md), then follow the [developer guidelines](/docs/dev/README.md). + +# FAQ :raising_hand: + +#### 1. What should I do if I have an issue while installing or upgrading Testrun? + + You can resolve most issues by reinstalling Testrun using these commands: +- `sudo docker system prune -a` +- `sudo apt install ./testrun-*.deb` + +If this doesn't resolve the problem, [raise an issue](https://github.com/google/testrun/issues). + +#### 2. What device networking functionality does Testrun validate? + +Best practices and requirements for IoT devices change often due to technological advances and discovery of vulnerabilities. You can find the current expectations for IoT devices on Google deployments in the [Application Security Requirements for IoT Devices](https://partner-security.withgoogle.com/docs/iot_requirements). Testrun aims to automate as much of the Application Security Requirements as possible. + +#### 3. What services are provided on the virtual network? + +The following network services are containerized and accessible to the DUT: + +- DHCP in failover configuration with internet connectivity +- IPv6 SLAAC +- DNS +- NTPv4 + +Note that this list is likely to change over time. + +#### 4. Can I run Testrun on a virtual machine? + +Testrun can be virtualized if the 2x Ethernet adapters are passed through to a VirtualBox VM as a USB device rather than managed network adapters. Visit the [virtual machine guide](/docs/virtual_machine.md) for additional details. \ No newline at end of file diff --git a/cmd/build b/cmd/build index d3294a681..8ecccb5ef 100755 --- a/cmd/build +++ b/cmd/build @@ -60,11 +60,10 @@ fi # Build network modules echo Building network modules -mkdir -p build/network for dir in modules/network/* ; do module=$(basename $dir) echo Building network module $module... - if docker build -f modules/network/$module/$module.Dockerfile -t test-run/$module . ; then + if docker build -f modules/network/$module/$module.Dockerfile -t testrun/$module . ; then echo Successfully built container for network $module else echo An error occured whilst building container for network module $module @@ -74,11 +73,10 @@ done # Build validators echo Building network validators -mkdir -p build/devices for dir in modules/devices/* ; do module=$(basename $dir) echo Building validator module $module... - if docker build -f modules/devices/$module/$module.Dockerfile -t test-run/$module . ; then + if docker build -f modules/devices/$module/$module.Dockerfile -t testrun/$module . ; then echo Successfully built container for device module $module else echo An error occured whilst building container for device module $module @@ -88,11 +86,10 @@ done # Build test modules echo Building test modules -mkdir -p build/test for dir in modules/test/* ; do module=$(basename $dir) echo Building test module $module... - if docker build -f modules/test/$module/$module.Dockerfile -t test-run/$module-test . ; then + if docker build -f modules/test/$module/$module.Dockerfile -t testrun/$module-test . ; then echo Successfully built container for test module $module else echo An error occured whilst building container for test module $module diff --git a/cmd/install b/cmd/install index c350a969f..906550abf 100755 --- a/cmd/install +++ b/cmd/install @@ -68,15 +68,13 @@ deactivate cmd/build # Create local folders -mkdir -p local/devices -mkdir -p local/root_certs -mkdir -p local/risk_profiles +mkdir -p local/{devices,root_certs,risk_profiles} # Set file permissions on local # This does not work on GitHub actions if logname ; then USER_NAME=$(logname) - sudo chown -R "$USER_NAME" local + sudo chown -R "$USER_NAME" local || true fi echo Finished installing Testrun diff --git a/cmd/prune b/cmd/prune index 9f471897d..4c2796460 100755 --- a/cmd/prune +++ b/cmd/prune @@ -25,17 +25,16 @@ fi # Remove docker images echo Removing docker images -docker_images=$(sudo docker images --filter=reference="test-run/*" -q) +docker_images=$(sudo docker images --filter=reference="testrun/*" -q) if [ -z "$docker_images" ]; then echo No docker images to delete else - sudo docker rmi $docker_images > /dev/null + sudo docker rmi $docker_images fi # Remove docker networks echo Removing docker networks -sudo docker network rm endev0 > /dev/null -# Private network not used, add cleanup -# back in if/when implemented -#sudo docker network rm tr-private-net > /dev/null \ No newline at end of file +sudo docker network rm endev0 || true + +echo Successfully pruned Testrun resources diff --git a/docs/README.md b/docs/README.md index 5f055dbb9..efea0d413 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,21 +1,19 @@ Testrun logo +# Contents -## Contents +- [Get started](/docs/get_started.md) + - [Run on a virtual machine](/docs/virtual_machine.md) +- [Network](/docs/network/README.md) + - [Network addresses](/docs/network/addresses.md) + - [Add a new network service](/docs/network/add_new_service.md) +- [Testing](/docs/test/README.md) + - [Test modules](/docs/test/modules.md) + - [Test results](/docs/test/statuses.md) +- [Developer guidelines](/docs/dev/README.md) +- [Accessibility](/docs/ui/accessibility.md) +- [Roadmap](/docs/roadmap.pdf) - - [Get Started](get_started.md) - - [Network](network/README.md) - - [Network Overview](network/README.md) - - [How to identify network interfaces](network/identify_interfaces.md) - - [Addresses](network/addresses.md) - - [Add a new network service](network/add_new_service.md) - - [Testing](test/README.md) - - [Test modules](test/modules.md) - - [Test statuses](test/statuses.md) - - [Development](dev/README.md) - - [Running on a virtual machine](virtual_machine.md) - - [Accessibility](ui/accessibility.mp4) - - [Roadmap](roadmap.pdf) +# Something missing? -## Something missing? -If you feel there is some documentation that you would find useful, or have found an issue with existing documentation, please raise an issue on GitHub by navigating [here](https://github.com/google/testrun/issues/new/choose) \ No newline at end of file +To request additional documentation or report an issue with existing resources, visit [the Issues tab](https://github.com/google/testrun/issues/new/choose). diff --git a/docs/configure_device.md b/docs/configure_device.md deleted file mode 100644 index 1db7155be..000000000 --- a/docs/configure_device.md +++ /dev/null @@ -1,31 +0,0 @@ -Testrun logo - -## Device Configuration (Deprecated) - -The device configuration file allows you to customize the testing behavior for a specific device. This file is located at `local/devices/{Device Name}/device_config.json`. Below is an overview of how to configure the device tests. - -## Device Information - -The device information section includes the manufacturer, model, and MAC address of the device. These details help identify the specific device being tested. - -## Test Modules - -Test modules are groups of tests that can be enabled or disabled as needed. You can choose which test modules to run on your device. - -### Enabling and Disabling Test Modules - -To enable or disable a test module, modify the `enabled` field within the respective module. Setting it to `true` enables the module, while setting it to `false` disables the module. - -## Customizing the Device Configuration - -To customize the device configuration for your specific device, follow these steps: - -1. Copy the default configuration file provided in the `resources/devices/template` folder. - - Create a new folder for your device under `local/devices` directory. - - Copy the `device_config.json` file from `resources/devices/template` to the newly created device folder. - -This ensures that you have a copy of the default configuration file, which you can then modify for your specific device. - -> Note: Ensure that the device configuration file is properly formatted, and the changes made align with the intended test behavior. Incorrect settings or syntax may lead to unexpected results during testing. - -If you encounter any issues or need assistance with the device configuration, refer to the Testrun documentation or ask a question on the Issues page. diff --git a/docs/dev/README.md b/docs/dev/README.md index f11b1b092..076cb827c 100644 --- a/docs/dev/README.md +++ b/docs/dev/README.md @@ -1,25 +1,35 @@ Testrun logo -## Developer docs +# Developer guidelines -## Table of Contents -1) General guidelines (this page) -2) [Code quality](code_quality.md) +## How to contribute -## General guidelines -As an open source project, we absolutely encourage contributions from the community to help Testrun remain an expanding but stable product. However, before contributing there are a number of things to take into consideration. +As an open source project, we encourage contributions from the community to help Testrun remain an expanding but stable product. To contribute, follow the steps below: -1) [Sign the Google CLA](https://cla.developers.google.com/): Whether you are an individual or contributing on behalf of your organisation, you must be covered by a Google CLA. +1. Sign the [Google Contributor License Agreement (CLA)](https://cla.developers.google.com/). + - Whether you're an individual or contributing on behalf of your organization, you must be covered by a Google CLA. -2) Determine the scope of your contribution +1. Determine the scope of your contribution. + - Keep it simple. Your contribution is more likely to be accepted if you change fewer files. + - Ensure your pull request addresses one thing, such as a bug fix, dependency issue, or new framework capability. - - Your contribution is more likely to be accepted if fewer files are changed (keep it simple) - - Are you going to be fixing a bug, dependency issue or a new framework capability? Whatever it is, ensure your pull request fixes or changes just one thing. +1. Reach out to the core maintainers at [testrun-team@googlegroups.com](mailto:testrun-team@googlegroups.com). + - They can provide confirmation that your proposed changes meet our objectives and align with Testrun principles, making them more likely to be accepted. -3) Get in touch to discuss whether your proposed changes are likely to be accepted +1. Fork Testrun and get developing. + - We aim to provide thorough and clear developer documentation to help you contribute successfully. - - It is best to get the opinion from the core maintainers whether your proposed changes meet our objectives and align with Testrun principles. +## Code quality -4) Fork Testrun and get developing +To ensure code quality, use the appropriate style guide when developing code for Testrun: - - We aim to provide thorough and easy to ready developer documentation to help you contribute successfully. \ No newline at end of file +- [Python](https://google.github.io/styleguide/pyguide.html) +- [Angular](https://google.github.io/styleguide/angularjs-google-style.html) +- [Shell](https://google.github.io/styleguide/shellguide.html) +- [HTML/CSS](https://google.github.io/styleguide/htmlcssguide.html) +- [JSON](https://google.github.io/styleguide/jsoncstyleguide.xml) +- [Markdown](https://google.github.io/styleguide/docguide/style.html) + +## Automated actions + +The current code base has zero code lint issues. To maintain this, all lint checks are enforced on pull requests to dev and main. You should ensure these lint checks pass before marking your pull requests as Ready for review. diff --git a/docs/dev/code_quality.md b/docs/dev/code_quality.md deleted file mode 100644 index 47eabcf95..000000000 --- a/docs/dev/code_quality.md +++ /dev/null @@ -1,16 +0,0 @@ -Testrun logo - -## Code quality - -Whilst developing code for Testrun, there are some style guides that you should follow. - - - Python: https://google.github.io/styleguide/pyguide.html - - Angular: https://google.github.io/styleguide/angularjs-google-style.html - - Shell: https://google.github.io/styleguide/shellguide.html - - HTML/CSS: https://google.github.io/styleguide/htmlcssguide.html - - JSON: https://google.github.io/styleguide/jsoncstyleguide.xml - - Markdown: https://google.github.io/styleguide/docguide/style.html - -### Automated actions - -The current code base has been able to achieve 0 code lint issues. To maintain this, all lint checks are enforced on pull requests to dev and main. Please ensure that these lint checks are passing before marking your pull requests as 'Ready for review'. \ No newline at end of file diff --git a/docs/get_started.md b/docs/get_started.md index f9a2aa2f8..dbe5eab43 100644 --- a/docs/get_started.md +++ b/docs/get_started.md @@ -1,123 +1,121 @@ Testrun logo +# Get started -## Getting Started +This page covers the following topics: -It is recommended that you run Testrun on a standalone machine running a fresh install of Ubuntu 20.04, 22.04 or 24.04 LTS (laptop or desktop). +- [Prerequisites](#prerequisites) +- [Installation](#installation) +- [Testing](#testing) +- [Troubleshooting](#troubleshooting) +- [Review the report](#review-the-report) +- [Uninstall](#uninstall) -## Prerequisites +# Prerequisites -### Hardware +We recommend that you run Testrun on a stand-alone machine that has a fresh install of Ubuntu 20.04, 22.04, or 24.04 LTS (laptop or desktop). -Before starting with Testrun, ensure you have the following hardware: +## Hardware -- PC running Ubuntu LTS (laptop or desktop) -- 2x USB Ethernet adapter (one may be a built-in Ethernet port) -- Internet connection +Before you start, ensure you have the following hardware: -![Visual representation of setup](setup/visual.png) +- PC running Ubuntu LTS (laptop or desktop) +- 2x USB Ethernet adapter (one may be a built-in Ethernet port) +- Internet connection -**NOTE: Running in a virtual machine? Checkout the virtual machine documentation [here](/docs/virtual_machine.md).** +![Required hardware for Testrun](/docs/ui/getstarted--2dn8vrzsspe.png) -### Software +Note: If you're using Testrun in a virtual machine, follow the steps on the [Virtual machine page](/docs/virtual_machine.md). -Ensure the following software is installed on your Ubuntu LTS PC: -- Docker - installation guide: [https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository) -- System dependencies (These will be installed automatically when installing Testrun if not already installed): - - Python3-dev - - Python3-venv - - Openvswitch Common - - Openvswitch Switch - - Build Essential - - Net Tools - - Ethtool +## Software -### Device -Any device with an ethernet connection, and support for IPv4 DHCP can be tested. +Install Docker on your Ubuntu LTS PC using [the installation guide](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository). The following system dependencies install automatically when you install Testrun: -However, to achieve a compliant test outcome, your device must be configured correctly and implement the required security features. These standards are outlined in the [Application Security Requirements for IoT Devices](https://partner-security.withgoogle.com/docs/iot_requirements). but further detail is available in [documentation for each test module](/docs/test/modules.md). +- Python3-dev +- Python3-venv +- Openvswitch Common +- Openvswitch Switch +- Build Essential +- Net Tools +- Ethtool -## Installation +## Device -1. Download the latest version of the Testrun installer from the [releases page](https://github.com/google/testrun/releases) +You can test any device with an Ethernet connection and support for IPv4 DHCP. However, to achieve a compliant test outcome, you must configure your device correctly and implement the required security features. The [Application Security Requirements for IoT Devices](https://partner-security.withgoogle.com/docs/iot_requirements) outlines these standards. Additional details are available on the [Test modules page](/docs/test/modules.md). -2. Open a terminal and navigate to location of the Testrun installer (most likely your downloads folder) +# Installation -3. Install the package using ``sudo apt install ./testrun*.deb`` +Follow these steps to install Testrun: - - Testrun will be installed under the /usr/local/testrun directory. - - Testing data will be available in the ``local/devices/{device}/reports`` folders +1. Download the latest version of the Testrun installer from the [Releases page](https://github.com/google/testrun/releases). +2. Open a terminal and navigate to the location of the Testrun installer (most likely your Downloads folder). +3. Install the package using `sudo apt install ./testrun*.deb` - **NOTE: Local CA certificates should be uploaded within Testrun to run TLS server testing** +Testrun installs under the `/usr/local/testrun` directory. Testing data is available in the `local/devices/{device}/reports` folders. - ![Installing Testrun in terminal window](setup/install.gif) +Note: Local CA certificates should be uploaded within Testrun to run TLS server testing. -## Start Testrun - -1. Attach network interfaces: - - Connect one USB Ethernet adapter to the internet source (e.g., router or switch) using an ethernet cable. - - Connect the other USB Ethernet adapter directly to the IoT device you want to test using an ethernet cable. - - Some things to remember: - - Both adapters should be disabled in the host system (IPv4, IPv6 and general). You can do this by going to Settings > Network - - The device under test should be powered off until prompted - - Struggling to identify the correct interfaces? See [this guide](network/identify_interfaces.md). - -2. Start Testrun. - -Start Testrun with the command `sudo testrun` - - - To run Testrun in network-only mode (without running any tests), use the `--net-only` option. - - - To run Testrun with just one interface (connected to the device), use the ``--single-intf`` option. +![Terminal during install](/docs/setup/install.gif) -## Test Your Device +# Testing -1. Once Testrun has started, open your browser to http://localhost:8080. - -2. Configure your network interfaces under the settings menu - located in the top right corner of the application. Settings can be changed at any time. - - ![](/docs/ui/settings_icon.png) - -3. Navigate to the device repository icon to add a new device for testing. +## Start Testrun - ![](/docs/ui/device_icon.png) +Follow these steps to start Testrun: +1. Attach the network interfaces. + - Connect one USB Ethernet adapter to the internet source (e.g., router or switch) using an Ethernet cable. + - Connect the other USB Ethernet adapter directly to the IoT device you want to test using an Ethernet cable. -4. Click the button 'Add Device'. +Notes: -5. Enter the MAC address, manufacturer name and model number. +- Disable both adapters in the host system (IPv4, IPv6, and general) by opening **Settings**, then **Network**. +- Keep the DUT powered off until prompted. -6. Select the test modules you wish to enable for this device (Hint: All are required for qualification purposes) and click save. +1. Start Testrun with the command `sudo testrun` + - To run Testrun in network-only mode (without running any tests), use the `--net-only` option. + - To run Testrun with just one interface (connected to the device), use the `--single-intf` option. -7. Navigate to the Testrun progress icon and click the button 'Start New Testrun'. - ![](/docs/ui/progress_icon.png) +## Test your device -8. Select the device you would like to test. +Follow these steps to test your IoT device: -9. Enter the version number of the firmware running on the device. +1. Open Testrun by navigating to [http://localhost:8080](http://localhost:8080/) in your browser. +2. Select the **Settings** menu in the top-right corner, then select your network interfaces. You can change the settings at any time. + ![Settings menu button](/docs/ui/getstarted--7cfvdpdnc5o.png) -10. Click 'Start Testrun' +3. Select the **device repository** icon on the left panel to add a new device for testing. + ![Device repository button](/docs/ui/getstarted--q5uw26tfod.png) - - During testing, if you would like to stop Testrun, click 'Stop' next to the test name. +4. Select the **Add Device** button. +5. Enter the MAC address, manufacturer name, and model number. +6. Select the test modules you want to enable for this device. +Note: For qualification purposes, you must select all. +7. Select **Save**. +8. Select the Testrun progress icon, then select the **Testing** button.![Testing button](/docs/ui/getstarted--w09wecsry3.png) -11. Once the notification 'Waiting for Device' appears, power on the device under test. +9. Select the device you want to test. +10. Enter the version number of the firmware running on the device. +11. Select **Start Testrun**. +- If you need to stop Testrun during testing, select **Stop** next to the test name. +12. Once the Waiting for Device notification appears, power on the device under test. A report appears under the Reports icon once the test sequence is complete. + ![Reports button](/docs/ui/getstarted--m4si1otdu5d.png) -12. On completion of the test sequence, a report will appear under the history icon. +# Troubleshooting - ![](/docs/ui/history_icon.png) +If you encounter any issues, try the following: -# Troubleshooting +- Ensure that your computer meets all hardware and software prerequisites. +- Verify that the network interfaces are connected correctly. +- Check the configuration settings. +- Refer to the [Testrun documentation](/docs). -If you encounter any issues or need assistance, consider the following: +If you still need assistance, ask a question on the [Issues page](https://github.com/google/testrun/issues). -- Ensure that all hardware and software prerequisites are met. -- Verify that the network interfaces are connected correctly. -- Check the configuration settings. -- Refer to the Testrun documentation or ask for assistance in the issues page: https://github.com/google/testrun/issues +# Review the report -# Reviewing -Once you have completed a test attempt, you may want to review the test report provided by Testrun. For more information about what Testrun looks for when testing, and what the output means, take a look at the testing documentation: [Testing](/docs/test/README.md). +Once you complete a test attempt, you can review the test report provided by Testrun. For more information on Testrun requirements and outputs, refer to the [Testing documentation](/docs/test/README.md). # Uninstall -To uninstall Testrun, use the built-in dpkg uninstall command to remove Testrun correctly. For Testrun, this would be: ```sudo apt-get remove testrun```. + +To uninstall Testrun correctly, use the built-in dpkg uninstall command: `sudo apt-get remove testrun` \ No newline at end of file diff --git a/docs/network/README.md b/docs/network/README.md index 0f97ecd7b..0efb5e3ff 100644 --- a/docs/network/README.md +++ b/docs/network/README.md @@ -1,44 +1,41 @@ Testrun logo -## Network Overview +# Network -## Table of Contents -1) Network overview (this page) -2) [How to identify network interfaces](identify_interfaces.md) -3) [Addresses](addresses.md) -4) [Add a new network service](add_new_service.md) +This page provides an overview of Testrun's network services. Visit these pages for additional information: -Testrun provides several built-in network services that can be utilized for testing purposes. These services are already available and can be used without any additional configuration. +- [Network addresses](/docs/network/addresses.md) +- [Add a new network service](/docs/network/add_new_service.md) -The following network services are provided: +Testrun provides several built-in network services you can use for testing purposes. These services don't require any additional configuration. Below is a list and brief description of the network services provided. -### Internet Connectivity (Gateway Service) +# Internet connectivity (gateway service) The gateway service provides internet connectivity to the test network. It allows devices in the network to access external resources and communicate with the internet. -### DHCPv4 Service +# DHCPv4 service The DHCPv4 service provides Dynamic Host Configuration Protocol (DHCP) functionality for IPv4 addressing. It includes the following components: -- Primary DHCP Server: A primary DHCP server is available to assign IPv4 addresses to DHCP clients in the network. -- Secondary DHCP Server (Failover Configuration): A secondary DHCP server operates in failover configuration with the primary server to provide high availability and redundancy. +- Primary DHCP server: Assigns IPv4 addresses to DHCP clients in the network. +- Secondary DHCP server (failover configuration): Operates in failover configuration with the primary server to provide high availability and redundancy. -#### Configuration +## Configuration -The configuration of the DHCPv4 service can be modified using the provided GRPC (gRPC Remote Procedure Call) service. +You can modify the configuration of the DHCPv4 service using the provided Remote Procedure Call (GRPC) service. -### IPv6 SLAAC Addressing +# IPv6 SLAAC addressing -The primary DHCP server also provides IPv6 Stateless Address Autoconfiguration (SLAAC) addressing for devices in the network. IPv6 addresses are automatically assigned to devices using SLAAC where test devices support it. +The primary DHCP server provides IPv6 Stateless Address Autoconfiguration (SLAAC) addressing for devices on the network. It automatically assigns IPv6 addresses to devices using SLAAC where test devices support it. -### NTP Service +# NTP service -The Network Time Protocol (NTP) service provides time synchronization for devices in the network. It ensures that all devices have accurate and synchronized time information. +The Network Time Protocol (NTP) service provides time synchronization for devices on the network. It ensures that all devices have accurate and synchronized time information. -### DNS Service +# DNS service -The DNS (Domain Name System) service resolves domain names to their corresponding IP addresses. It allows devices in the network to access external resources using domain names. +The Domain Name System (DNS) service resolves domain names to their corresponding IP addresses. It allows devices on the network to access external resources using domain names. -### 802.1x Authentication (Radius Module) +# 802.1x authentication (radius module) -The radius module provides 802.1x authentication for devices in the network. It ensures secure and authenticated access to the network. The issuing CA (Certificate Authority) certificate can be specified by the user if required. \ No newline at end of file +The radius module provides 802.1x authentication for devices on the network. It ensures secure and authenticated access to the network. The user can specify the issuing Certificate Authority (CA) certificate if required. \ No newline at end of file diff --git a/docs/network/add_new_service.md b/docs/network/add_new_service.md index b3fa22514..e6b01e102 100644 --- a/docs/network/add_new_service.md +++ b/docs/network/add_new_service.md @@ -1,21 +1,44 @@ Testrun logo -## Adding a New Network Service +# Add a new network service -The Testrun framework allows users to add their own network services with ease. A template network service can be used to get started quickly, this can be found at [modules/network/template](../../modules/network/template). Otherwise, see below for details of the requirements for new network services. +The Testrun framework allows you to easily add your own network services. You can use the template network service at [modules/network/template](/modules/network/template). To add a new network service, follow these steps: -To add a new network service to Testrun, follow the procedure below: +1. Create a folder under `modules/network/` with the name of the network service in lowercase using only alphanumeric characters and hyphens (-). +1. Include the following items in the created folder: + - `{module}.Dockerfile`: Dockerfile builds the network service image. Replace `{module}` with the name of the module. + - `conf/`: Folder containing the module configuration files. + - `bin/`: Folder containing the start-up script for the network service. + - Place any additional application code in its own folder. -1. Create a folder under `modules/network/` with the name of the network service in lowercase, using only alphanumeric characters and hyphens (`-`). -2. Inside the created folder, include the following files and folders: - - `{module}.Dockerfile`: Dockerfile for building the network service image. Replace `{module}` with the name of the module. - - `conf/`: Folder containing the module configuration files. - - `bin/`: Folder containing the startup script for the network service. - - Any additional application code can be placed in its own folder. +Here are some examples: -### Example `module_config.json` +## {module}.Dockerfile -```json +``` +# Image name: test-run/{module} +FROM test-run/base:latest + +ARG MODULE_NAME={module} +ARG MODULE_DIR=modules/network/$MODULE_NAME + +# Install network service dependencies +# ... + +# Copy over all configuration files +COPY $MODULE_DIR/conf /testrun/conf + +# Copy over all binary files +COPY $MODULE_DIR/bin /testrun/bin + +# Copy over all python files +COPY $MODULE_DIR/python /testrun/python + +# Do not specify a CMD or Entrypoint as Testrun will automatically start your service as required by calling the start_network_service script in the bin folder +``` + +## module_config.json +``` { "config": { "meta": { @@ -42,35 +65,11 @@ To add a new network service to Testrun, follow the procedure below: } } } -``` - -### Example of {module}.Dockerfile -```Dockerfile -# Image name: test-run/{module} -FROM test-run/base:latest - -ARG MODULE_NAME={module} -ARG MODULE_DIR=modules/network/$MODULE_NAME - -# Install network service dependencies -# ... - -# Copy over all configuration files -COPY $MODULE_DIR/conf /testrun/conf - -# Copy over all binary files -COPY $MODULE_DIR/bin /testrun/bin - -# Copy over all python files -COPY $MODULE_DIR/python /testrun/python - -# Do not specify a CMD or Entrypoint as Testrun will automatically start your service as required ``` -### Example of start_network_service script - -```bash +## start_network_service script +``` #!/bin/bash CONFIG_FILE=/etc/network_service/config.conf @@ -89,8 +88,4 @@ echo "Starting Network Service..." # Restart the network service when the config changes # ... -``` - - - - +``` \ No newline at end of file diff --git a/docs/network/addresses.md b/docs/network/addresses.md index 7fa71d716..261242687 100644 --- a/docs/network/addresses.md +++ b/docs/network/addresses.md @@ -1,20 +1,19 @@ Testrun logo -## Network Addresses +# Network addresses -Each network service is configured with an IPv4 and IPv6 address. For IPv4 addressing, the last number in the IPv4 address is fixed (ensuring the IP is unique). See below for a table of network addresses: +Each network service is configured with an IPv4 and IPv6 address. For IPv4 addressing, the last number in the IPv4 address is fixed, ensuring the IP is unique. The table below lists network addresses you might need. -| Name | Mac address | IPv4 address | IPv6 address | -|---------------------|----------------------|--------------|------------------------------| -| Internet gateway | 9a:02:57:1e:8f:01 | 10.10.10.1 | fd10:77be:4186::1 | -| DHCP primary | 9a:02:57:1e:8f:02 | 10.10.10.2 | fd10:77be:4186::2 | -| DHCP secondary | 9a:02:57:1e:8f:03 | 10.10.10.3 | fd10:77be:4186::3 | -| DNS server | 9a:02:57:1e:8f:04 | 10.10.10.4 | fd10:77be:4186::4 | -| NTP server | 9a:02:57:1e:8f:05 | 10.10.10.5 | fd10:77be:4186::5 | -| Radius authenticator| 9a:02:57:1e:8f:07 | 10.10.10.7 | fd10:77be:4186::7 | -| Active test module | 9a:02:57:1e:8f:09 | 10.10.10.9 | fd10:77be:4186::9 | +| Name | MAC address | IPv4 address | IPv6 address | +| ----------------- | ----------------- | ------------- | ------------------- | +| Internet gateway | 9a\:02\:57\:1e\:8f\:01 | 10.10.10.1 | fd10\:77be\:4186\:\:1 | +| DHCP primary | 9a\:02\:57\:1e\:8f\:02 | 10.10.10.2 | fd10\:77be\:4186\:\:2 | +| DHCP secondary | 9a\:02\:57\:1e\:8f\:03 | 10.10.10.3 | fd10\:77be\:4186\:\:3 | +| DNS server | 9a\:02\:57\:1e\:8f\:04 | 10.10.10.4 | fd10\:77be\:4186\:\:4 | +| NTP server | 9a\:02\:57\:1e\:8f\:05 | 10.10.10.5 | fd10\:77be\:4186\:\:5 | +| Radius authenticator | 9a\:02\:57\:1e\:8f\:07 | 10.10.10.7 | fd10\:77be\:4186\:\:7 | +| Active test module | 9a\:02\:57\:1e\:8f\:09 | 10.10.10.9 | fd10\:77be\:4186\:\:9 | +The default network range is 10.10.10.0/24 and devices are assigned addresses in that range via DHCP. The range may change when requested by a test module. In that case, network services restart and are accessible on the new range with the same final host ID. The default IPv6 network is fd10:77be:4186::/64 and addresses are assigned to devices on the network using IPv6 SLAAC. -The default network range is 10.10.10.0/24 and devices will be assigned addresses in that range via DHCP. The range may change when requested by a test module. In which case, network services will be restarted and accessible on the new range, with the same final host ID. The default IPv6 network is fd10:77be:4186::/64 and addresses will be assigned to devices on the network using IPv6 SLAAC. - -When creating a new network module, please ensure that the ip_index value in the module_config.json is unique otherwise unexpected behaviour will occur. \ No newline at end of file +When creating a new network module, ensure that the ip_index value in the module_config.json is unique to prevent unexpected behavior. \ No newline at end of file diff --git a/docs/network/identify_interfaces.md b/docs/network/identify_interfaces.md deleted file mode 100644 index 50e62acd3..000000000 --- a/docs/network/identify_interfaces.md +++ /dev/null @@ -1,20 +0,0 @@ -Testrun logo - -## Identifying network interfaces - -For Testrun to operate correctly, you must select the correct network interfaces within the settings panel of the user interface. There are 2 methods to identify the correct network interfaces: - -A) Find the printed MAC address on your interface - -Some USB network interfaces will have the MAC address printed on the interface itself. This will look something like: ```00:e0:4c:02:0f:a8```. - -Compare this printed MAC address against the MAC address provided in the settings panel in the user interface. - -B) Connect your interfaces one at a time - - 1) Ensure both interfaces are disconnected from your PC and open the settings panel in the user interface. - - 2) Connect your internet interface to your PC and refresh the settings panel. One interface, which was not previously present, should now be visibile. - - 3) Repeat the previous step for the devices interface. - diff --git a/docs/roadmap.pdf b/docs/roadmap.pdf index 9f4599ee1c45ade27a0d41342a25864b4dca69d7..0566598e08cc8b383aea4681eb5f087c51bfe507 100644 GIT binary patch delta 389766 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 diff --git a/docs/test/README.md b/docs/test/README.md index 3163b4c84..19fcf3fd5 100644 --- a/docs/test/README.md +++ b/docs/test/README.md @@ -1,7 +1,5 @@ Testrun logo +# Testing -## Testing -The test requirements that are investigated by Testrun can be found in the [test modules documentation](/docs/test/modules.md). - -To understand the testing results, various definitions of test results and requirements are specified in the [statuses documentation](/docs/test/statuses.md). \ No newline at end of file +Testrun provides modules for you to test your own device. You can learn more about the requirements for each on the [Test modules page](/docs/test/modules.md). The [Test results page](/docs/test/statuses.md) outlines possible results and how to interpret them. diff --git a/docs/test/modules.md b/docs/test/modules.md index 2fe5983b1..ff6bb5c53 100644 --- a/docs/test/modules.md +++ b/docs/test/modules.md @@ -1,16 +1,26 @@ Testrun logo -## Test Modules +# Test modules -Testrun provides some pre-built test modules for you to use when testing your own device. These test modules are listed below: +Testrun provides some pre-built modules you can use when testing your own device. The table below lists the test module, its purpose, and a link to additional information. -| Name | Description | Read more | -|---|---|---| -| Base | Template for all test modules | [Base module](/modules/test/base/README.md) | -| Baseline | A sample test module | [Baseline module](/modules/test/baseline/README.md) | -| Connection | Verify IP and DHCP based behavior | [Connection module](/modules/test/conn/README.md) | -| DNS | Verify DNS functionality | [DNS module](/modules/test/dns/README.md) | -| Services | Ensure unsecure services are disabled | [Services module](/modules/test/services/README.md) | -| NTP | Verify NTP functionality | [NTP module](/modules/test/ntp/README.md) | -| Protocol | Inspect BMS protocol implementation | [Protocol Module](/modules/test/protocol/README.md) | -| TLS | Determine TLS client and server behavior | [TLS module](/modules/test/tls/README.md) | +| Module name | Purpose | Additional documentation | +| ------------ | ---------------------------- | ----------------------------- | +| Base | Template for all test modules | [Base module] | +| Baseline | Sample test module | [Baseline module] | +| Connection | Verify IP and DHCP-based behavior | [Connection module] | +| DNS | Verify DNS functionality | [DNS module] | +| Services | Ensure unsecure services are disabled | [Services module] | +| NTP | Verify NTP functionality | [NTP module] | +| Protocol | Inspect BMS protocol implementation | [Protocol Module] | +| TLS | Determine TLS client and server behavior | [TLS module] | + + +[Base module]: /modules/test/base/README.md +[Baseline module]: /modules/test/baseline/README.md +[Connection module]: /modules/test/conn/README.md +[DNS module]: /modules/test/dns/README.md +[Services module]: /modules/test/services/README.md +[NTP module]: /modules/test/ntp/README.md +[Protocol Module]: /modules/test/protocol/README.md +[TLS module]: /modules/test/tls/README.md diff --git a/docs/test/statuses.md b/docs/test/statuses.md index d196fa4af..25c3d77b2 100644 --- a/docs/test/statuses.md +++ b/docs/test/statuses.md @@ -1,33 +1,32 @@ Testrun logo -## Test Statuses -Testrun will output the result and description of each automated test. The test results will be one of the following: +# Test results -| Name | Description | What next? | -|---|---|---| -| Compliant | The device implements the required feature correctly | Nothing | -| Non-Compliant | The device does not support the specified requirements for the test | Modify or implement the required functionality on the device | -| Feature Not Detected | The device does not implement a feature covered by the test | You may implement the functionality (not required) | -| Error | An error occured whilst running the test | Create a bug report requesting additional support to diagnose the issue | +Testrun outputs the result and a description of each automated test. The table below includes the result name, its description, and what your next step should be. -## Test Requirement -Testrun also determines whether each test is required for the device to receive an overall compliant result. These rules are: +| Result name | Description | What next? | +| --------------------- | ------------------------ | ------------------------ | +| Compliant | The device implements the required feature correctly. | Nothing. | +| Non-Compliant | The device doesn’t support the specified requirements for the test. | Modify or implement the required functionality on the device. | +| Informational | Extra information about the device under test | Nothing. | +| Feature Not Detected | The device doesn’t implement a feature covered by the test. | You may implement the functionality but it’s not required. | +| Error | An error occurred while running the test. | Create a bug report requesting additional support to diagnose the issue. | -| Name | Description | -|---|---| -| Required | The device must implement the feature | -| Recommended | The device should implement the feature, but will not receive an overall Non-Compliant if not implemented | -| Roadmap | The device should implement this feature in the future, but is not required at the moment | -| Required If Applicable | If the device implements this feature, it must be implemented correctly (as per the test requirements) | -## Testrun Statuses -Once testing is completed, an overall result for the test attempt will be produced. This is calculated by comparing the result of all tests, and whether they are required or not required. +# Test requirements -### Compliant -All required tests are implemented correctly, and all required if applicable tests are implemented correctly (where the feature has been implemented). +Testrun determines whether the device needs each test to receive an overall compliant result. Here are the rules and what they mean: -### Non-Compliant -One or more of the required tests (or required if applicable tests) have produced a non-compliant result. +- Required: The device must implement the feature. +- Recommended: The device should implement the feature but won't receive an overall Non-Compliant if it's not implemented. +- Roadmap: The device should implement this feature in the future, but it's not required at the moment. +- Required If Applicable: If the device implements this feature, it must be implemented correctly (per the test requirements). -### Error -One of more of the required tests (or required if applicable tests) have not executed correctly. This does not necessarily indicate that the device is compliant or non-compliant. +# Testrun statuses + +Once testing is complete, the program produces an overall status for the test attempt. It's calculated by comparing the results of all tests and whether they're required or not. The possible statuses are: + +- Compliant: All required tests are implemented correctly, and all required if applicable tests are implemented correctly (where the feature is implemented). +- Non-Compliant: One or more of the required tests (or Required If Applicable tests) produced a Non-Compliant result. +- Error: One or more of the required tests (or Required If Applicable tests) didn't execute correctly. This doesn't necessarily indicate that the device is Compliant or Non-Compliant. +- Cancelled: Either the device was disconnected during testing or the user requested to cancel the test attempt. \ No newline at end of file diff --git a/docs/ui/accessibility.md b/docs/ui/accessibility.md new file mode 100644 index 000000000..58d948522 --- /dev/null +++ b/docs/ui/accessibility.md @@ -0,0 +1,12 @@ +Testrun logo + +# Accessibility + +We designed Testrun with accessibility at its core. The application provides full support for: + +- Screen readers +- Keyboard navigation +- Responsive resizing and scaling +- [Helperbird](https://www.helperbird.com/) + +For a more thorough explanation of these features, download the [accessibility video](https://github.com/google/testrun/raw/main/docs/ui/accessibility.mp4). If you require further accommodations when using Testrun, please [raise an issue](https://github.com/google/testrun/issues/new/choose). \ No newline at end of file diff --git a/docs/ui/device_icon.png b/docs/ui/device_icon.png deleted file mode 100644 index 2472f7da223c4b6a29e94e830303095628d3aae6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 933 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 literal 0 HcmV?d00001 diff --git a/docs/ui/getstarted--3d9k3si3ul1.png b/docs/ui/getstarted--3d9k3si3ul1.png new file mode 100644 index 0000000000000000000000000000000000000000..5c762156d2173c891fafb99706b5aee40dfaeac7 GIT binary patch literal 10189 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 SET~Nn@cP-;vugM8-~S6=Z4fX3 literal 0 HcmV?d00001 diff --git a/docs/ui/getstarted--7cfvdpdnc5o.png b/docs/ui/getstarted--7cfvdpdnc5o.png new file mode 100644 index 0000000000000000000000000000000000000000..288e5f876724a11c0e6bd5d49ab2d40061f17dc1 GIT binary patch literal 17881 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 literal 0 HcmV?d00001 diff --git a/docs/ui/getstarted--m4si1otdu5d.png b/docs/ui/getstarted--m4si1otdu5d.png new file mode 100644 index 0000000000000000000000000000000000000000..f6b7eddc93ce83b8b340b1e5679e4db8545bae11 GIT binary patch literal 13909 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 NW@>c>f9X!-{{np0YA^r* literal 0 HcmV?d00001 diff --git a/docs/ui/getstarted--q5uw26tfod.png b/docs/ui/getstarted--q5uw26tfod.png new file mode 100644 index 0000000000000000000000000000000000000000..4b8b2e847a7994704b089d2ff465d8b2c2fd788c GIT binary patch literal 13929 zcmd6OXINADx2`j`u`((}L15HTga{d>DOE=ir79(nl0-$i1dxOtm{CC`lo5nT2}qZc zgc3qY6EUD5y#x{h5(p3ip%bLuSmuA`f1dl?=YBZ%-1CK$mGyF4+V^r#L|`Y>;81*|mfZ ze(6eMBWYu4YtKiLPbPmfi~n#h{-fJD-+JFW*4qyBzu5<#NDd}x7QwV-mZ=tDc=x(V)s~;9oi zA+yw`tn4p(YaEgUN(}Ck8!^E=7>UTw&c-bi10WsM%4cKJOXeG39%G{r56--OHMbiT+VpX`rekrGJt-Q4+;YYXg8ddw zeKBd`)+1@ca6_B}x(7!ob8Sr@zbD7YToodm1vfSg@IHKRsgjEyqG|jxP5zGn2VKo| z(c_GJ^{wfPe*hdLcooDosUYF>Ag_Df7g}z(+CLByUe=UgX*k^2Q=*jqac`>TYUj8) zC0bU$KGM+k^Xz@`nr}q5;zc_;tU|aShM*S^%Qp~ZF?V4_gSnQ4r6@F;;S-(@5?+y$ zYKW_zWejf`P*aCkqJCcdvrT5B%X&5TjEWB?B+h%Pv1hGyXHC=CSVaKFFgf@M*->${ zd6OkYXdMeZAaCnR+#bW-E?)4SXB)TMQL;5<)P%XQU&bX;J_ueV=YO}p!T&)(SmC|( zHzxUO-F&tgO@-Pm$o8C?sl_b~24U=qDE^?EP;R}%(F_Cf9So%>ncY>9!>w={vk1BU z=5o(yyeSUJUh3Jb{S#zk^Wq2$3WWn>P~G^+P#ku!ECxA_$mI-9Ut*Ubi>CU>Ogx>R zlm{W9ZW9~Vaz>J1zf59R@YRnP=>Mv2wwVqtArC!M zX6Bgo?37m@wiMbAkpdIVRy~ z4flD4-S8EWD7*8EuM;zg4KCvbE5p0X^66($1(f03$` zctx!IC$sKXjG~;JcM4(mO2GT&Jw_n=tc|i>Gx`eRktD3%3u+?oO@;jAyDnvr-VgyY zt1?)9-ZRv9K!P)~)l;cT{RrM+&Z=jbG~DTW;pjXxzjz3F5+4{76jZ!^%=WgH_dxs+ z(p{cPz`A9gc>t!unV;n^f6lq^TuqoWS++3Hz5o3f>}hPF?`-gcD@%QfwhQ*e3y`}% zY}xXMy16(=M|~c|IC;k+9Z35T?lSVu4Ff^lCSWsPUg16N;8kOXStb1rhqnXt_vww~ zUv*gN87~cRYbS$bjE%dWRdJDmDgAzF{BKe`KcHK(vQ7rdpnViN?q%pT080-y~8 zvU2h~QY7|euMPyCHz+OdDfPtPp)XqT)`y+p?#@d4jg1{XJoDz@BkA&k?fWO8^72!v z_&|4=%P~b~w(c}csU9ff0g3|#4XcNyfaLzUQ#qCj2cH6-JiA7sB2@ib+zPMSz1iOKRTY1eAVK3qIb-@i=Hws0d(xAZEb_U-ts zv3JYi4;aOWzM}SRJ5}P<$ z5f{58)+9ar4pG;7NXQ{)XBUq68|l%FdVC$mD&4#uJ<*e{0ogmwb{yG_92|nk*iLE? zB!ObX>=9A)iPMBTp} zssCJA=R`WHCGn`dpyKw>@qkH8UcO)lkyqT!n3}AM%VRAZQRnm>6bCv=WATtSHZx8l zfPBD%r4wt$%>Ux6R~OvcxVfsbmip^N@P5S>eXYV{FTB`X6Y$NVO@CK4#Ae{!XiV~~ z6vG&|kYJOUBJLq;JcKq4@j3qEa)MIRD;i`Lt8W;k^~P{K>G!}S>hSkU8zBotC8wv z(-MrMRjxbq%y;wUKvYHG3tjRM%|D!ol2>cXQ_~K6vy663Ub%lYqsz;}{=*A8!u5Rd z`7&nA^x5FY04No9bV@BlopyO;rFGCdY4qeFEZv;ziM(6Z_N_fxV3xN3lS(TDbV>`;3KRi zzMOMq_Dcy$`64c{*#1s~5;Zk3o`+T+ZUUJTI~YLc+)y?@;o{_~ORnlm-7|ne95((4 z-Ba+R+)~dU#nCNte;tt!3wXuP`Vx^_F6w{H;{S%@YPlu-rT62XfB)&2_|F9J{;Wxf z7n?Nxi&%>-uMb3lfBZIm!XydXz6RPB+G*&n)8est%a+ial!G_KGxL@$%UOXZUwzel z#%R{$;#X&ULUR-U_&QO%kzoLSVC)g!_XqI5S|A}92=&};+56tzsOQeI%c|{Q=;|Gj z?UFXfLxuzDY$Ixzo0DlbziC-m$<+*Mzoa`o^y6wD$8)klA2jyj^q^Zx3Q2fbWDEOl zR(H^B2k3RrW$j*IFriu5bWw_f5tbW}{`gx=Csz|bP{}`saDZPZ6s6TzHeZH(+qfB9 zSBB@uNfew(r}IG~vJ}XW&ymmsA|t+e0*sXedO4e5@NNe@(%hgE9!<<6Hz_JAlOydX z{O8^n00znIMp5YHue^ zPseij@5q7t9O;+`W?(C;`JABn?w5?FM7#-9x=@=cDVP;%}qvV#*8+LZ02J? z;$?d~jJ~dPP5qhpIz3ZLRk!PX(01eU$n}y{4_#1bM}4ay(&e3X`?GT@RwB*G zNBS2!3oJC^3}lM}`7iO~=x;K9a!)Bh&UVaNS6s-fj|(X-?Ra3TT)t#^zj3dkmUEmO zD6H(bU`5HqvS!Hn`A~PvX#3bL!c_oN=lRu>z{7tVlH0@Z93f`eJS+UXOH)DDwxmaR z9`4OJXG*P@taa1{XQJmYHL{cQB+yuCw=E#-X3v2Svkl`7Pr z@{!&x$``snEZNku0;)a=Q6c&A1|YcM1dSCI)M~HYjB%MRpN`ESu3`D=C)L;w&Zs*| z-+w=#Us}^n=>cU+5loQ?R|t;Ho9Q{ro?5)H zvmb=ZSaftskXGmlI@z!GjHlGRKR@av93ZQJbwOqY;lvMg-_(1jEmqHPGTp%^er_dg z9e=yv5B?mH_hbqSl<58Qgfpq)^}Btmt*gvxeq4IuK%?eT`8qtXM2<`Ex1N8S|DPqb zKd{C=-jkGtgTzrP+2+&j*U9R2A_bLl`JE=XoZ`0|!q|lpHdF<{?8Ha4^Fc}#MzhDi zrTlVU52Gm0fwFP_G%72)b{ea8%W$HU<$~9{uLRJbb}CIA)>o%i6g9oa*>(POB_AtE z3Ow-5C$j2dBQ*8v1qnR0M1(&1Q=cLST4LZRo73LU*@Ku zNvP;{M1%PxRT9O#DeXbx3+J)*S`#g=_sI(1Q)r^ACh$X$gv^K}w$UodouY^?_)uCnKTDKkj_Ma7QDBqSDbxj-)?&>+#+H?e( zaP+#=>R?7drWCHV^@y=7FlH8d#Ht*1T&>2(N`%yI5dSn9?1D;KxjK5mX60y?C4mArO<@zrN2Aw)Y zH*nL30f||4&dGO7E00zV=Il<2dP#b$ys|TiIi+)ljvpC09J1e8ansgL!#l-TvAHx* z9#ZpubaOKQLWIiGdckAa_Mzmp1*Ihp#?LR$A$1(RMsgtA@r4k(y2T&pzOz|r0_}VL zIEa@weeFTZOiJD<0GSXi>%3D-dSoxbJv7gnBXw5)6xE{fv#LFNkr`0YXMKw7D@jOk zop`*fJ9q$Pl_0tc$=yUr^kz%!E5mw%s(EWd-RUw*!l!_>GWxPGDo64h0oO3E1_Y*y(MXaZsX8&(TwOCgsL6j&2kT*-MCGkD6ik3 zHFyYqLts@MKrj8MDI>Yc4wKofB;H=bLuD?!{c;9j2~&e9(1r_DC=8i$_cOntf9W}L z>0Ooig-S2A%s}UcqQL8dOu0#Kc$2DrOnR4|`#`8>1GmC!{6w{L;VA|nQFW`r`((IR z7VrV;Wn2iayLc$Zj`HweRcOmm&v5sKv{_AabC7>4^;NDHl%(MrXg;itoxnfkpT#Y6 z_W=4T>SCqf<<-{tHFrEmQQ`s5$mZH9>LRFRrpC;C0F6u(SVgSOOBjB1gjU|B$@f#v zjs;hkFBqq;G!_9VZW8)!K-WNsQCv121ow=eP@ASPOzr0S@)YrBLo`v@aDCbT{x!u@*$s-0u3b6JOMI}ai*GUd2BmBmLb1p=lO3$Ya(kIG#u)3x zU-UZz1?Uo+l&v&Xp-uKH;adPJ$EP{v7>6TWYS(LLd}cd#f9M@Ewj(cLs`W06oYkc{ zg|rYC7;O_-rjxVA3P`f1@{p7PZgStG;TwqY@G08ot1kUJr)i!L@_-x1dm66Y?~_g4 zTw=o`MkK~6OGp?;aU;eN%eq3NgD2cQ+4x6tOYgETJMy2lJUKVCsNGM9#vm#E>9RU! zJe>nNit-8?IC);Wsan2SWZ{r%>SSivI!7;nABsb1P9=Oq zfWkGIa2j7)d&&V$YsR&KG_j3B^_Q_>ALvUdf~hRh6vwYfVB>0JCL*hp@yb`H^zW~E z5_pn?s)kHX>&n8!R9-&wWUoiw_*u?_c^o_qD%0%wo0{-!gmvG%WOS!=bPJYH+Eehk zCLf$p42xJ~xuf&b zG$<`^QzfKK3-IJx>gL155ZvpD@?%vQ7Zg$Vn9z_%tGYE5k6ZmAXVO>{|3`|lZmRZ> zdtU}bV!X$bV zp^7Ell9E`R4$tn8UN?K~VfQrEU&9>P*YhAxbghGOikjq`5qBx8HKc!dk5!-O+RQ)~ zWxUjQEVrgCR&3#B%*3{s{^Z;+w)u~WF5ZPNMh@Xg+sB_I{VHe7V{(ueim zHulfSx9eVaSEv)@soWZB+6pz@+_I=x(9juTR&ySjR1@AjtlBEw+d2)MisJVy<}=mP zu~mIx*qXTV4Pt$BT;8GR47wY+;_!>GqhtCnz02tzRsF6DuKNx)_+~8nHtk>lbO#uw z_06j!mNx=O3mi|oo-$&vjTnDmuY?Q`yt{jKWtK77xMh` zn|70r5e0%UeZNR{aQ2;cRcMXS7L4>ILnqlK1Gs3jVD*4KjK*p|&Xc4t=YD$To5(y+ z+QUSMRJ{cjkKkPafPyDixO?Gp9IM>V-GoW|+Rg=HeC}$GQ`^q|hM-{%!r-W(!D2+P z(I|6s2Ei3t!x~o}cArQ6F){o4K8SQ&+Po@|&$MaIFo<5H7||z-f{)7POBJ=Z>J5)V z08Ds<|0+n^0#Kf!9n%>VI1~I%KhKAop1(#ItNsjGy&)u;2o!Qd44^gy4qlKtZmi0)Ak4Q=73?fukSR&ZNaEu%*VUX#x1$J zRM4!&!v=7ws+^5s^}z;Os*uTa38~{$wOWL2(n8G+Odag4H*Fz41iz4t&hqV6g|_oF zh5FJKUNG{wG*ET(QtgePj#UuZdSXtK>4*t#b|KRf(sgk;9(Jl0QrJ)GW`MKy&$@>R zlMUD^(dFM_WQ?PIS{cR#B8o{;efPbfaMZ$KQRq%WOj69Jif0ZR3&S=yej^?gs0RjY zu+mx|7bZnixj*TAftm7|jY?%!F|7(F=Fg^$&nt+QvkLB`!M5{O9NskP`SvA=06gjW zv=<2?SH7p^r856+nR56O`N2dr+eZUtn1oboqY=@1Pt8NEyKhegP^)3ymHRyXd%dks zUOB0H`WF=?LyA*H%(_oG^#T$N2=NY08+e;zH9y?ND^-M-RvPwA)UVUKkT88Dgky11 zMQ5;vhA)E1=7*Oyo1f^_FDYE@6h^d~o3sjCVP2J4s^rO{?Q9M&e@@GpG-rgy_Rp;iDp1UB%ABwgq>QKk^kC-k0#trnNcW!QO}Ccy(*F}r&MBKV@v zzj79Hs-}mlT34!^A8`}0I?B*;Cgsb+s!VW=cH1 zokrTWFqH^>-nPC}m2KSPmCf+nSm47Im>*X=IF%;1oyle~M>lfxb!HX_T|*r5!lfLE zoZD$2(gL6cUY}R>eN4<0}HNmIHZB{y4j77uPFw;yv?&~$C&v@W^H5=eMG`=nuwn6+j-mAQh6X8i3JBDe?%fla?Vncvev)Pa1?sW8Q({H`r z6ibVSg$X}@ta8mbEF=|2PiZC;mvY@ahUyFd`uv3C?~n_kv(&-&fUO{Svbr;@~w4!*@zxBZMqpa@aP>?@h#6BV_Wi21TjwvkeZ(H zI}ql@O`XP6YJ)g|A=}_(Mssvyj&X)r7$?OU3^eU z#iX1GSFuq!!}6F95I^A*MH0$wn>r(|lZW`x%xooZe}-=?uyl2~u4 zV^W361#h#@l{<4{&3vQD^odE2!>Z7s&+gj44mOu>&{i4ygh2ofRzU@-vk=-|(j?N<|3P8bf=GICIk{ z^%MHnwUhZaOMhm;HYywV!fT@}D34cJPz^>7^w;gAGG#wmqDrpo4o078`%WyZu^V~GEblTUIR z-*YaMT?l%Ywaa@p>eQOEqUbyVf_v?b{xj?|cwtZI3`@W)f1()rp1Oa<*6`Myry*uT zpPz=LD^Tbe)Fm$3u5Mxb0q75%z{(-tT8wf3d8+oq-zVb57X89+GR0)iANaNAfpjeB zZUvX1c#LQo@4tOAU~pYr-Kj;QigaVK_n^^N9VApj^b?Y>Zb46lSH*AY)#z&~BGd5S z@FWI+|D`ZPbz{)dKrb&cl|7~9y`oWxG>N-BUsUGnok0`oXrhD zV4D#)bjAD7m-<^AXsg8$hv6j~g?_cLxMDx@TESm#%Z#`jwDBfWtCuc<43kExR)M{I z(x{thLhLTN<@&2&Ee$n$3Ni7TLn#7c6uvpsr54@J&Apoqk$=?0ipt?iVh5dc>Av$h zkbSmqYVt;TspkBkSTfySiePO7s5<)5d(7r@m)i5%D)9pV4=8g#VAvK~0pOB5I~)q% zKBL8&l%(g-hiP%y8Q>w2uWeIgJ%idni!12lp47pcC{?^}>Dw<*sR43aAxAr+S#S7T}wF0jGV3 z7b=C?I=~g%TdmJXplx+TwB<~CG(IgNduRozyIgt+7T>K5{W=zeMv4}hAX(gvQ>wbY zuzC~6+1;BRg^$5P!^7HYev0DnQ6X_g)~QU3XVO5bAu>9ebb4alq0IBiDy6Llz03O^ zCT7AQKwR=--D}EMv3Ap~KHLF`_SV{&vRer}NCtUG_1F;el>6sS0$0by+}WTvLRn8y zTS8K(GuDey9$2Cy3)gcEFj$PvU znlzZx_p?S7kQhlma6Ve4hggQ33(K|`(?}VBVk9| znKyU!ByeeQivbJsDr*LKkG`MkZ5I_*4f0yh;EVabOnPQ6WHYQkTNq&2rJ?D?c`~g$ zJggyptoObPwRpZeUxzF!48l#o$~@xTW;>!D(+~?^B5B3hnq12N!)a>Z6$gPRHbL0e z?u^=xV8RXT+ZK%^CtdLnUz^&=%{VP(_Sqw@?pq|tUOdMQtygSy)tf_JMZ-AM3qeY(#|Oj zMHZ33xqz?(1gYetXybn2Z?)d}&N%;2Kt`~d27$HxdS$0c9PQ#Gsk2jXX%0i_8WIie zA8r%mz#23*?cLiw^I^RU{Q`~mD?G#VRbarL8hrX{Rf8lpt}tCmDiC+xbGz|;rs`!@ z@Xux^;M-5a)Ak!M;r1$Oth<>_ER`(Z;OWAj_#2w7Dcb9A$J4WEqMF3tnoXC_o=(xG ziXIN1LvVk9z=pG?IpU%3OK(mT!7Gt|WOIH+8*sdKW7W6Gq90Rw5KI|3qT7Sh6HrS~ zya$y({#@Me>4}a_Xs38mVlxX01C?#54R4t@Jyux-f#aG#5qdq+7n<6ID-7Ro8ppVVLIR{2XUqVNMKGR#&oYQGu>lp`5$+2wx$vcdpY6wbf!e7m(5yIGDyO%Zt(Cj{Z*H5)Lq#DD~Wa6_7L}HCiS0qRKbeQDr39 zti}@SYvFu#c!8IvdMLO!m{u2O45+PL)z#aKqHW_O=UEX7X#I%2f3<|=`L>JTRe{Rl z!t1lJ(SxW!CqtD`wL9p+^?w)N4jGgW=rY7|=lD*5C<;{dsQd?y6ki75+tZgW?#jf7 zP73A|X*wq=+r-kxE(6l;^x9*t-v|t^_%VlV6u$N}#PCY~0f_W)lE`O)HGYAqCwKy! zIw%w76`w@^G+AQX{C9Up_Qk55uxgtb$f9YU>ZZy&vSwbgmd|j9b|p*?ahAA z3f<3-THb0e&QF%TKAsC%d8mFsMa4zs7gnNW?%4B6Ef><7n`a-;iYeh=*QxeY4EmGV zd;^=}&`L3ORiFUOY1McQkw=;_Y1R!d8Jxj(EV;i=R@3pMS)EN9;^6`8`VCBx14S}X z4;u?}*ru6buifKU=o_qn1YJwKC?6nw-)OIryS}n;_54bZRG4Ces@#z1WshjV;8_b( zq`U~Fq|~_BQLHX#ct+}8x=OS;>AcapQM=#9_ZPU6(hB3^PZ!Fw@8&Cl9yYO#%NQK_ z>Db6B29SQsm3q}xvCh0>*TyjyBiE~4!pF7lQ$>e*5@Zd9KZ$a!^CW z&3yr9-;J;|ywk)cHYz(d*+bpfPxh6@xAmobLt&0-?qL9GMVWyS)@8^9c{^fT?3uNc zvhTR0d;~Y~(<+Ga{a&QvSN!;I6zwY}{S7aA>ird#{tjdhiWk4}_m5~_EF*F&0s>n8 z1FHOTu9%CQdqYc;L4QNPJ2WoOnzXqw1mn;D)45xWCKZ)pikoH2o~z$9aS#3`G|}p2 z=<%`bC%&z#LH%O1%j`igt#|Ib1qu5ZfJ;s?!2GPY^7A~4s@lH&yT0o0{-D5rR)zlQ zq093WofBVC_CHR={GTy%F{ET3F&%3C{~2`u2Z}GI_$*CwJlA*ZEm^`KOOD0qy9}a4 zkOh^N%(^F&yiz9|>WL}NNB~l+fo#;QjoTjj+@k%B3qaVR3JnlVBr97rI>kw#p^Tnl zh^M>`@?#D3Jdl1)D>&g6ZQlGFm!PON3T$K)QE|VM#UgpT?%aRW=j22UUi%)zb#Mcf z+_5;`c8o-O^+rs;^I~cb_O2!jwnm49H;xpu8M9dL?;M7GAWCCX6U4m(9x3K%`G&(d zxu!kHNd4I=xsCqUW95-MweI)c#;JJ>`(NtPD69*hDSsn6V9$!lR3sLW+G`IBeJ+KO zd;(8aJ?+|=XBih;_~Kt1qT>_=zH=v2&fl{Vp6S5rP3FHxGIvlBiA zx~X@Ff!iBU5WjV#_|D*fezGvwo3iMFu*nS_OZ`q3cqB!T*9{oCKie^=gQSd)$YEf} zTsdf!lkrGTK1cLsU8plpPgj(;RUZ7D_l^@`ZX4BBOfVzm6(`lB+PwaFt7TZ;~|?9sJ|4r^7ycR zh^Wk|A&z{9RZ$`^*RCES&}Sp%@_bqAH|n@^jDFzTggBo1;P2nJ=~5Ka7NSFbix|HV zT>19mMop+TMWl~9SNA1Iwkn48t}IMBxErL-f9CJ9dDI%Z=kHKZhRPGF`lX6HIy#Dn zYLoX?c;4x4&o=h+SGOT~52hoj6En%)ng6m;kRGyH^H7Rozt}?Nt4r<=pZHKbR0JZl z^igjd(U~kQ7T7-dH|gTXPk+Ys&5LCgug-dJgocdvxpGQKQ|!JToI^RD9@HeJn(j@k z97HJzg1d+QX%Ob)27&~6;J#X{dJF7`O4JRxW4qvuEbwth+xd*vrOf>^ysjvE=f z*7oX8E&jS_YG$aez35(Fxoe#M?8uBPmhsy^0>N$8WADEL{J)4SV=Lfx+gMxwUomxT zr?`t^Jnjo|>A#1o^N+IeKa@@M4-NNOQT$Q!F)<42_2QTnV8gcV=xwmhw>L5}T7XY% z|Btv0=lh69O{QD(UHqqbNeYDt4oM|Qmy|wqZwQ?|BsSRWVo7b7}G;vam7!q-j60ulr}Q z+>6$orx~7YpR64y^>Ka}%o#|EA8Xl7Ocxd3@wNKADsX?nmEv%158eN&YFpRWp87?p zWe@Qc#@Vk5rj%b*hU%DRZrtPq}5i(6~RjVdarKF{=8Z!akJG$$=S$k4w zvbcX$(-$|xwyu2n*Nn;9Keys26uk#iE8ojo(IqsqL`k920JRE|O;q^+!%Um>pd|Xe z8)6kJpX}I&z^odc|HZmq_Tb-ntqRp12u%RoOyrd{={n;@+?Va}TTd+HIhz%!nX%%^ z{dc1z26jIt#TFnICpz=nO)%g2kUCE2FY^j@af$8EefMnq^^WhF2%^tGQ?eDGqh@jvu4G7>6U zfcQEOMy~aI&`eT=NmGNV-{`6{$a*PYAH^9Bfc%h1==KCX!MFQ-PdMyzhQRFmnDAfJ j?EagnrvKY~-7rj6R;EW-p9Xv>G1rYOuHb*a{qTPQe)%4{ literal 0 HcmV?d00001 diff --git a/docs/ui/getstarted--w09wecsry3.png b/docs/ui/getstarted--w09wecsry3.png new file mode 100644 index 0000000000000000000000000000000000000000..e379040b59473f5035bbe2e76c6b1727d78f43ea GIT binary patch literal 14001 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 literal 0 HcmV?d00001 diff --git a/docs/ui/history_icon.png b/docs/ui/history_icon.png deleted file mode 100644 index eb95a8663bd7e574b30466f2fa9461a174490a83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 538 zcmV+#0_FXQP)ZgXgFbngSdJ^%m#tVu*cR9J=W zmp@R#FcgP>cy8?;X3Lz;z}(^}J20y&?xG`u(1T6x68>o$9h&xcvWPX7*x3Q4}eYd!Cn=!xbzO zg7Dy&+4gTb#S*uUxh-xVcxB0;(`utE@~R7&FXmvrFHH>i?AD=;cTg}7|j9 cvyywk5BJY^UKz0?dH?_b07*qoM6N<$g68$~zW@LL diff --git a/docs/ui/progress_icon.png b/docs/ui/progress_icon.png deleted file mode 100644 index c326d185e93a58a3f2194200052daeb83e209bb3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 989 zcmV<310wv1P)ZgXgFbngSdJ^%m%Z%IT!R9J=W zn7>ckKorNnsk(8@F>{>R_8L2kal!}`sjAefNC*j9q^kb|5@M{{5d^bMTx2HpVhZA4Ok2a#Hyzwr&_xZl}?%i_=gTY{cXJoIQ8em>~0baH(419iZiQT<@ z^rBe0Picakx9@O%aXImfqc2~vySER<*vx(x26kHa=tVKUeg7fdKfAa@pC-7vxs?L> zK?pwx(Tif-weFV-><1zGlmY;HQ7i=tf)D_}7?T1y?T0nj^JezF5HNS9C`Bm{7evU) zL?AAR5CT64XD*9{fIIFG;OOu`3d99{K0J^DbzBd9N>MdUH0&Ax;OgdfIlxZq9*i+G z>>8?;nFC^s2|??Yi8ouDIiQZ$1pru91y#!g00d#U9NYSirq;Cd|;JH5Tl1kCO;KRz8W>|!%VK6=N)r*itTPVOsMm1aY)S?3TILtC zYMK}~D_xsb7Bd6iwbJO?c5aUlg0iZj7sXORSs*_MarEgRx4)4oi^qEM%)qU*zIeTj zo0AHXMUMj!LM8%bv)~6m{|uPFT)L)Vl@RG~+_bk=EmI0aX@as^0%J@%Fj1O_7RgI% zVPGc{-Q$zf91s_zswGgGNP&(|PVw=>djOD5a91}&$Z1dYOu+ozUsDuxTrZb0F6hnH zCa#;!9MEtoi@+EIAw)Vbxn36?w=16NNfX>uU>?0}ZH}jm3o>*K{WQW^ATH=v`T~v* zOx#B=jzs^SG{H>;=FTju0z=nwDdU5Tu4!0P6e$pIRfeuffwCEr`Is3vJIE9;>;z@C z1mGcMe2`gPC2(486bcvt;=@u|Eg=X(&~%Q@0`Uwb>72oR$R@bgj{)~-A`U%1IUU)H z(u?H>!4TZ_Mw&iZgXgFbngSdJ^%m#tw}^dR9J=W znZHxQFcilho?Ehqne3^n&b0g!fPX~66>(Jm34YA#NMWYTGTY8%Z|69ul{*4yV}sXT zK2z`A>+AQDmwc0JLkJo(?s#G{o9dP+Og&rT zeY-t~#c?dn<($Lr+tG@WXCdY?j$<3Wj9|5d*?gWmf1g?!hjN55Lg2aXL43zKY$dHi zDV64zhVE*$yF^a3jiX6OHuaR!EFMJ>e8gV|^(*E8DZ?!GDSOH$vuV~U!7?bjd<|P7 zNfLN@dp()sFh{!Shnr0y6X|z*${e-eG*ufxSME~lFobm&DncrYK)^Q%y$3)002ovPDHLkV1k_3?lu4b diff --git a/docs/ui/settings_menu.png b/docs/ui/settings_menu.png deleted file mode 100644 index 046526b2546214541f5c97f8635f8fb7d8c9bc8b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37230 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 diff --git a/docs/ui/test_name.png b/docs/ui/test_name.png deleted file mode 100644 index 3d18df19d031cfacd7e1befe745efc57be0109cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10221 zcmd5?by!s0w;ohNkP-x>OG-i|B?W1zp`>x7q@^2FQW_Lc5Re(VyAdftdKh7-0R*Ln z?&fa3?{}a3@4f$Bo@Zcl&YW}h+Gp)|z3W};y_SX&F(EA>1Og#eQI^++K(0%JaRdP# z_{~u+%?JM6aFbKfB>+GE1eOut|GVxA2JSk}*6v;~S1X8(le420pPPlNm6em*D`)r3 z>#g7p%n%iMSzYfh>lkwf`q2!|EpxmfJW~-*Tz4ddn0pg*%w{Ho*XQ4tCcokQT6Fgyl&+v?v#)d!Cxr=bJ3e}9?1X|Uk(Jn!T*f%WZrsWgV)E8RKWEpVyx_wU~U|UI!|bI&MJRMg zda10Fmm7|uWR-ht4^F?yk|V#~Q{?C8x3@o91PsA{M+y#qCkqI0bQceU*q$zaz;-yv z{rBPZ+2)6991zRJ?xDJHObE%pQ)FqQ6zyp`#m8rJ%V0YK*FzvrHdy`_11m=V$S&4& zF_)K?7k8q%ip?MWb8+;i0+=Wm;q~oGPEJm;cDwYr(7T$}EuAK^C&0O~3wZ_x1`^m+ zp4u*rjZsb14mcus`@m5?Ufx?JF&RtHCJe_^?a$q2i}jCIpD7hs6ritQFc_EP4agfd z0%=G*U9uJUj`YF)=;)|coO9ijrQujb+4Ca3M61UzYpvI3;@Y3%gHg#JSkW&&S7?uprj`Ek)?aJ430)sD$D+yplJHw~tjF^@#Xwag1zM zzh>1$wsn3s=d$$g=sAw;u@@Zr6?uL;Ubue|h z&>I;U5eoa>4)K^&N2xhdMk>>vz$+cotZi(5K8V&0#zi7GRnYsL-P@9Jw>p^nD)UF} za&C`|9Gzc-{9$-U+G(AdyJ@Pajw17a!Ig-|)Y@(HxNBfnvQU@jGv?bWO}^{2+>=9>5F$Lxe3-D30UXau%(y-XNGquJ8)nk{f3 zZwwaOYytwL4sc7f3-1o94eXQCPfxs;O@Q4G!t$+eA*lR&98 z8{D#mPkS28nYb`pLhQWs0+vSSM&AMIWLK#&O zfxBpUJXT2**INNj;lM`prbPd~E8Y9p+{4gg)9smO`rGQ1YU7*sZf=75bQ2H32D+t# zfiP2hvq{?we!02tZT4DK@CaCa|Ni|7svk;FUaC-r-{rWF_G&LHt56Y9Q8vsRAw|EX zVeaUto>M;X0NoOOiP3&}6t3e`u4>Nh24#g$sDIt;asGT0oks2zytE)>Hp#v6u zdO;;o9*f0h#pT)3CaK;tGBn&yZy%U6;7?&2O5BnhI&4m4=ia|Y3jL^h4_NV8hE!lr zH8G^+1LSM>zp=x%2VO4w%i;)zdrL^YPjZhe;dKbB>hcPeE(@RPO*?(sG#d{OVg2LR zR3q}OW%;Mm5t9>kHLRN37O?l!c;k$UP)A3{lrkvU8%)PQ+Mr6DASDJ zT`N`HCJfcL6qRe+tE)VR&5?puQPkqQGb_~U3LmW=qdeRp4>EvLy;$4MJ&|Ae!V%!# z3Xo%WclT}7poeK5z%yO(f~KaXZ`}!2kCV7H;TFXuCAiYkTx!`BN*cpqHLyt)b`oZl z*=PFYZx5THN_ak-wll$TuXtGHNFhhEVDc3yKXfy**%Z&;T_a69&@DsjOfdt%7#SQK ztbjub3v)+q>gy#*tWsEK%L#e#4%BeLJw?(6wA$W@zRTfKwcEU>`HRr;1C754`fnJW zVX_Y91vO;ndKdAv^2&nPg?Io%8@yHAJcWgxy(J{tO&F5VS{1{&6(2@N2M6~VQpIE| zZOTw8YU=Ru#^3Yl_Y>LozjQ2&8_s)JcIao`_HbP&)GzwwAQB*v11l=*%CR-FiTlc) z)PBYlIZx->?mp(T>1NbcY+Pfs@5q^!OkFJU|;APG|ZD=$w_LE+@YZk~A)Cbs5~4EaJ43fJ)H6VHyJMSCaB()WQ^+UPTE(Kj7DYkq>OH&i})Y~vQ4mT4x zUKmV%L%t&B9&v|qbK2Qgc7_U-Mtrik!oqB<_~%@eN?z2-i7y8ooJ4ziX9iB5RjkNi zX?Sps;h0zxEa~-!FUS+~Ht%}TUwAI$>+(!*<}mEBNT38V5P9Zk+r{a}vzxOGo=Hx^ zYFAY0J72Km)2)?}S3=rN6xc?7+f+sHT562c{c(B=9c@bmXoR|5@(p-NvgNnLwk0*| zpDP=bVm)~hiLoSwOFlv}GEA#WXZ?eNVS$V?vS|*tp)Woa=obkI3O;Qtja#2J5}i>j zll4Z}$^Q=0gmOpTmN?pyuIkV)BVzSsLfmjdzS3kItP7;0qnjQQQAfGb9qi<4jjen} zcLk(&3z)&L);-hAFDdZM-|q=uaP!lxEc{c42&$PMR)&CGq%RNpl<41Tzzn5HBrC;#Ai5^a%&Z)I!CT{Dad znuayc+G0$HRLwREqoPsOh3rekuRaPos5mepX_NA?=^VBJn#0n12yYU(vJaYfVnZ{^ z^kuR&sm&RV>x_Nla*|CXVl;mtLZ zfVw#=q2P>-WQMQ{BXux#HL0%|ZWKjy z5|Lp3!u$R{dK}AkOWT1ZuxOMJBh9G)Q1#b@7G)@^aY!S#4x zLDfv2d9iTCD%ExBWqF!xvu$Ys+l~H35V;(N-r3nvuDNr>_}H&@>9W=6JpQx_Sby$) zaV_BV=Y9s+jrh(^yv(miN@#e35|nasdYaOc8L)zyni{RBE}ty58}g_{p~`{2GU8O3h{TwyZPvX8vJtI9g(Kwx%VO%H5NJGbN zETw{Ttqye6sl9a>Q&LF|OZ%6>nwK-Q7{JOYN{(Sn?af;hxgXt|FpFmS zw>ly`SQ6RatD)9Dm$oaa{-~M%Sc)z1`n1EmVfHW*J`4hVU8*4!gKokDFO-jLhNyZ0 zQ(5C!{e`;aTgQ-IirQ5sfur*C+_M9CBwKz9wATjPr~yL!bZniKp`{^}ZR*%WL{fmX zMC?<5c8bq@*i|A;EfpzWMRvV}YhUuN<+cbv)deu=EfuouQdjmwHmmrJIEl>K|x7V zu{JXGBcH!@XFR(~GhP2yZVrV%2Ft*TK8m1H?ydZynsvS7K|O=!497=|JarYjpIJG# zYa~40%CNes>S-7(+6uo+mxpEVXxKvvy0WWX(8(}L?kM+|H!!GA#j7Q=6OJoc47s%_ zsG~PJjCccIaR`HD*XBMG;!}lw?7?B{kGavhrNfh?jNb2;BP9>&HM@qiA#iwD6i@)XGW}Kei zNt_K7-K)qvpCc6Ab7!g|t1M*5c4iHZ<8lu%4Wdx2G5YJ7@veQ;Yc`eZ*bm-H$1c=f zjpj2s!oHlV5i#01HA1TQcNP7a#pF8;FQH}VhP08&^9F#g7Uu!@NtY>BkIM;yBVaTj z{o$bl+fx;=@%Mu@Vrf4s?NZi!q=v92)WLFjR)*{k{jUPhWkq?{9b_K1{FPvuy zXe^!Q75za5dgy1U%Stg|NUisgLfvLzKsb}Ke(v}KJ9ka5K_a`0L#^(=N{`6~tEC+lPMqP+YdCM8dxwu$NqnI*5c~pCZZ~C0+~hDUY`*s3hhC zjtm?`sYJg>&{wZSzrd{Qiv2A4$)c##QIuv%D0ZW~aAd}|C)h?~LHn~G-R*oE1qnYc z3e|%k>Z@qEF*Ab*I>$H%-2Z_Av`0E-VJMhO4??)lY%BV= zet~TF)sZB1GwKfOwmuv{_5&o1@4m4Qocujp_;r!xrlC$y_^~{UW zXL}R(1yBrwJ>3h`0?1vafG?^?K6~w<8JCwk1ER|pI6>8ayVut{ei~ux69`dm8G{7XDO7=^&%jQ@p#{GP?J&J1c@Owivg_9BvZgHeUMFA3SlTxa05X=~+Mf3=!f% ze|ma4J;$b!xYdp-nQ-vT^(~i(16^Zey45#KHM=ja3hom$RBJ^fFN*WG?A}6ZENBnZf+z$si0T1pJi6~@oMksblrxdPb6!SfbNa(#tO2}Qph z*y=B#J7(zX9i43hbW&`Bq^AxBvsoOG zL}-ueheJPk_S{iIF-9f&e{N4WxKC+L2b2Js`OQp3H7WbL6AfOSX>oiz*KbolV9)Y zMWd{yXurrC6ubAHrY8gi&4`a3h`dVG)*n*5MW`8YZ@I8F4` z$?O2NgcQ5pjNn*;lIn-so!hUdPgukcV9= zkoxzQkBOwRf+Lp4n9ici0Jvjr>Ig0kBCD88vY2Y?R)BIWp!_k_~TvFBo%Z`s6GL%1X5{v zX^v80fXJ^@1~`Ju<8tne^(<959~|*!=Wm*G$mH7IMH^1Q_9ETh+GZI6n!jS0!n#UF zc~%~m35oloc3qyj4?5aX7HnmxTcBI-g(B{FvbFNTQ|oS5;wmC+-H(-($BMzzRDPs8 zF0DC)k##ZfkR@o$sCF1ng>}1A;EV z`IW0TfGT4uSy&-`;UJ|wzQoCTva5r-V!XCkuhtwO6RjYLsC}<#{X*ZE_>@1#X7u$l zdJd(I64KU7T3lKRn7ux<+f%zf?Rh4TH(GGFX662ZoxOeEAr`9m@=V-#K|GOyT{BBb z$T!_G(m1|2*L-7hv)k7MRDMuGeq$burD-Y^v_rcgbPXJDkr_X)miC{R0v5brWo1xW zOooBJlMrvs0EU^G!WLhuEF8x%9G2kwLGDR$WZS@d4Jo#%^MF2@VE#NF4~X1mWp1rQ zSgl2d&H40$2+iasl;-W3AFZ%F-0f?x7^$_Exc2}ZpP&>~bSJTLS&*wH`GBSx+EvyY zwLtR??LL~weNuf~^NK@(3>u-QCwwT%_WSKszToT}2eMM)d(4*mXC40PbI$YcNH>E& zs#Ev8!#0A7$n7Qy3?O(0yt(>+^K^NLixD#Xc*qGCdVDr%ZWZzQjx^U#*%YFYJCwfT ziba7VU*SY2>G&AT}uv0d%iUmzW+eK5awzlhrp@?e64 z|0ut(cYQA{_p?fOs{wDKmEl#8;)VcGSF6Hc?W4pU^_vlLiP>uRr(+g+CO{I^F#D{i z>VsZ+hJ0YqQ}}KpQ(4vHLaf+)kIpg+?KGvR0yG32AtW;^k2QWLC+U0u>?jBvi#R~5 zXwNk17rD&#RUbaP26+Jp}xuo1AGc9=%vnvK52~|Dqs1WB2?=9j|h?K14)4zIxPnZyNs_| z70u24#IHc^x$}62LccE?dCnxGk)w%AtJ*js{E8~OEklE&l07tRLDxDdk4kxoAGb;Apd z{D;Xq*ez9ij1V1E2NI~Lr z-rtf=WWTvYDw?LAnwhETjwB0Z1k~mJW{0Vs-fxEPmldD53?lf&p<`kZ;*qwPI}Ged zhOzp$C>MIC71IdL7$j}0#hAVAOo}Q9pI*Htr z8g3&X5PY|*UI6}xNdsQ027AAV`LZ_Kx~XZpAj4!f{GRr>sVVo@yLeZDPR9S6)Y{?` z0t&ssM&klqI8d*gaOuZj!nfE)UQ6G%Wag3o%B4CNS^_qMz){8$^6X3!R0mgBfBP`% z@!Z+yQqvn%PA7mW163LTQa%vNBslJ~XW33FJJK5Sr!=%uA7rHE-t{fJs+)$vnD?@y zpI?YNZ2exx`4~P&Q5~iQrA$pvAIRRJMnV(fh5CL+jqPVkb(NUc7~Khs1=3TpRp>Xx z@}ZWm9&*sa&lSL{;iRS5k+n64ycpRf?{SgJ67uZ zkBH6Xb#Qca*<=P4-PBxAvH~SzphbgPK=(%Z`o>10Bd7!GzxXR`nEZ78R#_p>N+W-< zZwG&KYA&=ucOEYR)K7t7Z>g*Dy|xUp2xcCtqu2r3T+{Qi5=7#@reRlEb=d3e2;>Zd z#lyL}XLd0dQ*gmm&0`3+Hsx2nIxhXa=0kpmh(*1Sv=EY%1+mW;n0qT^hgG~$r^liZ z2{?Lspj1n!;vW>`kQ|Ku)I> zJ&B`?&&kq~u6-|8|3jPUv=%Jcdjg^2Y7dLtSbFaYwP5(mrQ_`|wQ)w|7M`&Dijr+Z zK?%LDn^$|wc=)6&5H^H+(DWq)8d|(PwiL1+eGhY;pi;D-+vyx=F^h<2P#NsGL2{C< z#DYmjBW}#Wiz;mhO~`vvX?Z24#py?4V^7x<4>EdC4nWO`-Ac{Pc_fFx z`r4(_&S`Q+?_6+PnbGA$c~qi6`-@UkX3hC=iLmyXo78&3oY2%~eLBMz`x0SrE}C_m zH*fE*2un8;${KoVQz~SE92dA1P?KDk>^raP~peu8p;I{btni zd9(#hP>`(T(5~Z$SJx*41DEtacH4v=epB>m4QH4e@eQo)34+101lRdgYpRONJuBe& zut=Oe9lazfDyl10Pw}pygyG+j{B_-bYJobA7Ki&qemoYb2rL95t3l7m$Vk#ej)*Qv z0~nMpEO<^$rT|q=wn|M)i!!+_epBRLh+Zy5jU#x7PN<`nl&uS9N%k_8 zQQzR@)v1Y_kVFeq_B_>#pGKGaS(kV>Px~!kFAd7FCLCPLEY^1I;~g#S10M{;ECV4p zpmA%YJPjwl0Mu5LIf+1b&40RM3_vTw?P%4lHrlKH9SEdH>Yt95|K*_nsUWJu=^FXJ ztuD6)e#*Vg)re8dBV#7gL7~{(V*hQ1xrG!M%wX`OfIJdBziQ3N`p?57{^{ho>ZU>N zQiuIdHx20j!9D3Jd$@Z2*lnla8X#C;^2oPyR}DxHEQbPKTUn0DLb9*&(UvV_H|W|S z{ZAi`-=8(ZlhIA`KxU>~4RsWdZ`A&=HmCc22l#=BUb`|fVq;?+X;ap$2J#x>tG*+( zR`1x1)4ATBP)62&@08#MB;~6nrt|t1^}|*f8c#P!$ki=@C0FDP)cBTlU9jQ*&Z&K4 z9kz$BIK>saaaD=FdIsp}a`~rW>i@W;|EKl;fBWT@OVa+~s(kj?p!-*gp`xH6Un=+F G-G2dumwj~r diff --git a/docs/virtual_machine.md b/docs/virtual_machine.md index 2f029b296..579a40c93 100644 --- a/docs/virtual_machine.md +++ b/docs/virtual_machine.md @@ -1,38 +1,41 @@ Testrun logo -## Virtual Machine +# Run on a virtual machine -This guide will provide steps to use Testrun within a virtual machine in virtual Box (VMWare and other providers have not yet been tested). You should use this guide alongside the [Get Started guide](/docs/get_started.md) - only differences will be outlined in this guide. +This page provides steps to use Testrun within a virtual machine in VirtualBox. VMWare and other providers haven't been tested yet. You should use these instructions alongside the [Get started guide](/docs/get_started.md). -## Prerequisites +# Prerequisites -### Hardware +## Hardware -Before starting with Testrun, ensure you have the following hardware: -- PC running any OS that supports Virtual Box -- 2x USB Ethernet adapter (built in ethernet connections are not supported) -- Internet connection +Before you start with Testrun, ensure you have the following hardware: -### Software +- PC running any OS that supports VirtualBox +- 2x USB Ethernet adapter (built-in Ethernet connections aren't supported) +- Internet connection -Ensure the following software is installed on the host PC: - - Virtual Box +## Software -Ensure the following software is installed on your virtual machine: -- Ubuntu LTS (22.04 or 20.04) -- Docker - installation guide: [https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository) +Ensure you have VirtualBox installed on the host PC. Then, install the following software on your virtual machine: -## Installation +- Ubuntu LTS (22.04 or 20.04) +- Docker + - Refer to the [installation guide](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository) as needed. -In addition to the install steps provided in the Get Started guide, the default user must be added to the sudo group. -1. Open a terminal and run ```sudo su``` to login as root (you will be prompted for your password). -2. Add the default user to the sudo group by running ```adduser {username} sudo```. -3. Restart the virtual machine. -4. Continue the installation as per the Get Started guide. +# Installation -## Start Testrun +As part of installation, you must add the default user to the sudo group: + +1. Open a terminal and run `sudo su` to log in as root. +1. Enter your password when prompted. +1. Add the default user to the sudo group by running `adduser {username} sudo`. +1. Restart the virtual machine. +1. Follow the steps in the [Get started guide](/docs/get_started.md) to complete the installation. + +# Start Testrun + +Follow these steps to start Testrun. Keep in mind that attaching USB Ethernet adapters is different when working in a virtual machine. -Attaching USB ethernet adapters is different when working in a Virtual Machine. 1. Ensure the 2x adapters are attached to the host PC. -2. With the virtual machine running, right click the USB icon in the bottom right of the window. -3. Select the 2x ethernet adapter names and check that these two adapters have now appeared in the virtual machine. \ No newline at end of file +1. With the virtual machine running, right-click the **USB** icon in the bottom-right of the window. +1. Select the 2x Ethernet adapter names. The two adapters should now appear in the virtual machine. \ No newline at end of file diff --git a/framework/python/src/api/api.py b/framework/python/src/api/api.py index e8e87465d..0d633987c 100644 --- a/framework/python/src/api/api.py +++ b/framework/python/src/api/api.py @@ -26,8 +26,10 @@ import uvicorn from urllib.parse import urlparse -from common import logger, tasks +from core import tasks +from common import logger from common.device import Device +from common.statuses import TestrunStatus LOGGER = logger.get_logger("api") @@ -35,8 +37,16 @@ DEVICE_MANUFACTURER_KEY = "manufacturer" DEVICE_MODEL_KEY = "model" DEVICE_TEST_MODULES_KEY = "test_modules" +DEVICE_TEST_PACK_KEY = "test_pack" +DEVICE_TYPE_KEY = "type" +DEVICE_TECH_KEY = "technology" +DEVICE_ADDITIONAL_INFO_KEY = "additional_info" + DEVICES_PATH = "local/devices" -DEFAULT_DEVICE_INTF = "enx123456789123" + +RESOURCES_PATH = "resources" +DEVICE_FOLDER_PATH = "devices" +DEVICE_QUESTIONS_FILE_NAME = "device_profile.json" LATEST_RELEASE_CHECK = ("https://api.github.com/repos/google/" + "testrun/releases/latest") @@ -45,32 +55,45 @@ class Api: """Provide REST endpoints to manage Testrun""" - def __init__(self, test_run): + def __init__(self, testrun): - self._test_run = test_run + self._testrun = testrun self._name = "Testrun API" self._router = APIRouter() - self._session = self._test_run.get_session() + # Load static JSON resources + device_resources = os.path.join(self._testrun.get_root_dir(), + RESOURCES_PATH, + DEVICE_FOLDER_PATH) + + # Load device profile questions + self._device_profile = self._load_json(device_resources, + DEVICE_QUESTIONS_FILE_NAME) + # Fetch Testrun session + self._session = self._testrun.get_session() + + # System endpoints self._router.add_api_route("/system/interfaces", self.get_sys_interfaces) self._router.add_api_route("/system/config", self.post_sys_config, methods=["POST"]) self._router.add_api_route("/system/config", self.get_sys_config) self._router.add_api_route("/system/start", - self.start_test_run, + self.start_testrun, methods=["POST"]) self._router.add_api_route("/system/stop", - self.stop_test_run, + self.stop_testrun, methods=["POST"]) self._router.add_api_route("/system/status", self.get_status) self._router.add_api_route("/system/shutdown", self.shutdown, methods=["POST"]) - self._router.add_api_route("/system/version", self.get_version) + self._router.add_api_route("/system/modules", self.get_test_modules) + self._router.add_api_route("/system/testpacks", self.get_test_packs) + # Report endpoints self._router.add_api_route("/reports", self.get_reports) self._router.add_api_route("/report", self.delete_report, @@ -81,6 +104,7 @@ def __init__(self, test_run): self.get_results, methods=["POST"]) + # Device endpoints self._router.add_api_route("/devices", self.get_devices) self._router.add_api_route("/device", self.delete_device, @@ -89,10 +113,9 @@ def __init__(self, test_run): self._router.add_api_route("/device/edit", self.edit_device, methods=["POST"]) + self._router.add_api_route("/devices/format", self.get_devices_profile) - # Load modules - self._router.add_api_route("/system/modules", self.get_test_modules) - + # Certificate endpoints self._router.add_api_route("/system/config/certs", self.get_certs) self._router.add_api_route("/system/config/certs", self.upload_cert, @@ -115,10 +138,15 @@ def __init__(self, test_run): origins = ["*"] # Scheduler for background periodic tasks - self._scheduler = tasks.PeriodicTasks(self._test_run) + self._scheduler = tasks.PeriodicTasks(self._testrun) + # Init FastAPI self._app = FastAPI(lifespan=self._scheduler.start) + + # Attach router to FastAPI self._app.include_router(self._router) + + # Attach CORS middleware self._app.add_middleware( CORSMiddleware, allow_origins=origins, @@ -127,10 +155,27 @@ def __init__(self, test_run): allow_headers=["*"], ) + # Use separate thread for API self._api_thread = threading.Thread(target=self._start, name="Testrun API", daemon=True) + def _load_json(self, directory, file_name): + """Utility method to load json files' """ + # Construct the base path relative to the main folder + root_dir = self._testrun.get_root_dir() + + # Construct the full file path + file_path = os.path.join(root_dir, directory, file_name) + + # Open the file in read mode + with open(file_path, "r", encoding="utf-8") as file: + # Return the file content + return json.load(file) + + def _get_testrun(self): + return self._testrun + def start(self): LOGGER.info("Starting API") self._api_thread.start() @@ -191,9 +236,12 @@ async def get_sys_config(self): return self._session.get_config() async def get_devices(self): - return self._session.get_device_repository() + devices = [] + for device in self._session.get_device_repository(): + devices.append(device.to_dict()) + return devices - async def start_test_run(self, request: Request, response: Response): + async def start_testrun(self, request: Request, response: Response): LOGGER.debug("Received start command") @@ -214,9 +262,22 @@ async def start_test_run(self, request: Request, response: Response): device = self._session.get_device(body_json["device"]["mac_addr"]) + # Check if requested device is known in the device repository + if device is None: + response.status_code = status.HTTP_404_NOT_FOUND + return self._generate_msg( + False, "A device with that MAC address could not be found") + + # Check if device is fully configured + if device.status != "Valid": + response.status_code = status.HTTP_400_BAD_REQUEST + return self._generate_msg(False, "Device configuration is not complete") + # Check Testrun is not already running - if self._test_run.get_session().get_status() in [ - "In Progress", "Waiting for Device", "Monitoring" + if self._testrun.get_session().get_status() in [ + TestrunStatus.IN_PROGRESS, + TestrunStatus.WAITING_FOR_DEVICE, + TestrunStatus.MONITORING ]: LOGGER.debug("Testrun is already running. Cannot start another instance") response.status_code = status.HTTP_409_CONFLICT @@ -224,23 +285,17 @@ async def start_test_run(self, request: Request, response: Response): False, "Testrun cannot be started " + "whilst a test is running on another device") - # Check if requested device is known in the device repository - if device is None: - response.status_code = status.HTTP_404_NOT_FOUND - return self._generate_msg( - False, "A device with that MAC address could not be found") - device.firmware = body_json["device"]["firmware"] # Check if config has been updated (device interface not default) - if (self._test_run.get_session().get_device_interface() == - DEFAULT_DEVICE_INTF): + if (self._testrun.get_session().get_device_interface() == + ""): response.status_code = status.HTTP_400_BAD_REQUEST return self._generate_msg( False, "Testrun configuration has not yet " + "been completed.") # Check Testrun is able to start - if self._test_run.get_net_orc().check_config() is False: + if self._testrun.get_net_orc().check_config() is False: response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR return self._generate_msg( False, "Configured interfaces are not " + @@ -260,12 +315,12 @@ async def start_test_run(self, request: Request, response: Response): f"{device.manufacturer} {device.model} with " + f"MAC address {device.mac_addr}") - thread = threading.Thread(target=self._start_test_run, name="Testrun") + thread = threading.Thread(target=self._start_testrun, name="Testrun") thread.start() - self._test_run.get_session().set_target_device(device) + self._testrun.get_session().set_target_device(device) - return self._test_run.get_session().to_json() + return self._testrun.get_session().to_json() def _generate_msg(self, success, message): msg_type = "success" @@ -273,24 +328,26 @@ def _generate_msg(self, success, message): msg_type = "error" return json.loads('{"' + msg_type + '": "' + message + '"}') - def _start_test_run(self): - self._test_run.start() + def _start_testrun(self): + self._testrun.start() - async def stop_test_run(self, response: Response): + async def stop_testrun(self, response: Response): LOGGER.debug("Received stop command") # Check if Testrun is running - if (self._test_run.get_session().get_status() - not in ["In Progress", "Waiting for Device", "Monitoring"]): + if (self._testrun.get_session().get_status() + not in [TestrunStatus.IN_PROGRESS, + TestrunStatus.WAITING_FOR_DEVICE, + TestrunStatus.MONITORING]): response.status_code = 404 return self._generate_msg(False, "Testrun is not currently running") - self._test_run.stop() + self._testrun.stop() return self._generate_msg(True, "Testrun stopped") async def get_status(self): - return self._test_run.get_session().to_json() + return self._testrun.get_session().to_json() def shutdown(self, response: Response): @@ -298,20 +355,24 @@ def shutdown(self, response: Response): # Check that Testrun is not currently running if (self._session.get_status() - not in ["Cancelled", "Compliant", "Non-Compliant", "Idle"]): + not in [TestrunStatus.CANCELLED, + TestrunStatus.COMPLIANT, + TestrunStatus.NON_COMPLIANT, + TestrunStatus.IDLE + ]): LOGGER.debug("Unable to shutdown Testrun as Testrun is in progress") response.status_code = 400 return self._generate_msg( False, "Unable to shutdown. A test is currently in progress.") - self._test_run.shutdown() + self._testrun.shutdown() os.kill(os.getpid(), signal.SIGTERM) async def get_version(self, response: Response): # Add defaults json_response = {} - json_response["installed_version"] = "v" + self._test_run.get_version() + json_response["installed_version"] = "v" + self._testrun.get_version() json_response["update_available"] = False json_response["latest_version"] = None json_response["latest_version_url"] = ( @@ -383,7 +444,7 @@ async def delete_report(self, request: Request, response: Response): if len(body_raw) == 0: response.status_code = 400 - return self._generate_msg(False, "Invalid request received") + return self._generate_msg(False, "Invalid request received, missing body") try: body_json = json.loads(body_raw) @@ -395,12 +456,18 @@ async def delete_report(self, request: Request, response: Response): if "mac_addr" not in body_json or "timestamp" not in body_json: response.status_code = 400 - return self._generate_msg(False, "Invalid request received") + return self._generate_msg(False, "Missing mac address or timestamp") mac_addr = body_json.get("mac_addr").lower() timestamp = body_json.get("timestamp") - parsed_timestamp = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S") - timestamp_formatted = parsed_timestamp.strftime("%Y-%m-%dT%H:%M:%S") + + try: + parsed_timestamp = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S") + timestamp_formatted = parsed_timestamp.strftime("%Y-%m-%dT%H:%M:%S") + + except ValueError: + response.status_code = 400 + return self._generate_msg(False, "Incorrect timestamp format") # Get device from MAC address device = self._session.get_device(mac_addr) @@ -409,7 +476,15 @@ async def delete_report(self, request: Request, response: Response): response.status_code = 404 return self._generate_msg(False, "Could not find device") - if self._test_run.delete_report(device, timestamp_formatted): + # Assign the reports folder path from testrun + reports_folder = self._testrun.get_reports_folder(device) + + # Check if reports folder exists + if not os.path.exists(reports_folder): + response.status_code = 404 + return self._generate_msg(False, "Report not found") + + if self._testrun.delete_report(device, timestamp_formatted): return self._generate_msg(True, "Deleted report") response.status_code = 500 @@ -433,7 +508,7 @@ async def delete_device(self, request: Request, response: Response): mac_addr = device_json.get("mac_addr").lower() # Check that device exists - device = self._test_run.get_session().get_device(mac_addr) + device = self._testrun.get_session().get_device(mac_addr) if device is None: response.status_code = 404 @@ -442,13 +517,16 @@ async def delete_device(self, request: Request, response: Response): # Check that Testrun is not currently running against this device if (self._session.get_target_device() == device and self._session.get_status() - not in ["Cancelled", "Compliant", "Non-Compliant"]): + not in [TestrunStatus.CANCELLED, + TestrunStatus.COMPLIANT, + TestrunStatus.NON_COMPLIANT + ]): response.status_code = 403 return self._generate_msg( False, "Cannot delete this device whilst " + "it is being tested") # Delete device - self._test_run.delete_device(device) + self._testrun.delete_device(device) # Return success response response.status_code = 200 @@ -488,7 +566,7 @@ async def save_device(self, request: Request, response: Response): ) # Check if device folder exists - device_folder = os.path.join(self._test_run.get_root_dir(), + device_folder = os.path.join(self._testrun.get_root_dir(), DEVICES_PATH, device_json.get(DEVICE_MANUFACTURER_KEY) + " " + @@ -507,10 +585,15 @@ async def save_device(self, request: Request, response: Response): device.mac_addr = device_json.get(DEVICE_MAC_ADDR_KEY).lower() device.manufacturer = device_json.get(DEVICE_MANUFACTURER_KEY) device.model = device_json.get(DEVICE_MODEL_KEY) + device.test_pack = device_json.get(DEVICE_TEST_PACK_KEY) + device.type = device_json.get(DEVICE_TYPE_KEY) + device.technology = device_json.get(DEVICE_TECH_KEY) + device.additional_info = device_json.get(DEVICE_ADDITIONAL_INFO_KEY) + device.device_folder = device.manufacturer + " " + device.model device.test_modules = device_json.get(DEVICE_TEST_MODULES_KEY) - self._test_run.create_device(device) + self._testrun.create_device(device) response.status_code = status.HTTP_201_CREATED else: @@ -553,14 +636,17 @@ async def edit_device(self, request: Request, response: Response): if device is None: response.status_code = status.HTTP_404_NOT_FOUND return self._generate_msg( - False, "A device with that MAC " + "address could not be found") + False, "A device with that MAC address could not be found") if (self._session.get_target_device() == device and self._session.get_status() - not in ["Cancelled", "Compliant", "Non-Compliant"]): + not in [TestrunStatus.CANCELLED, + TestrunStatus.COMPLIANT, + TestrunStatus.NON_COMPLIANT + ]): response.status_code = 403 return self._generate_msg( - False, "Cannot edit this device whilst " + "it is being tested") + False, "Cannot edit this device whilst it is being tested") # Check if a device exists with the new MAC address check_new_device = self._session.get_device( @@ -570,15 +656,22 @@ async def edit_device(self, request: Request, response: Response): != check_new_device.mac_addr): response.status_code = status.HTTP_409_CONFLICT return self._generate_msg( - False, "A device with that MAC address " + "already exists") + False, "A device with that MAC address already exists") # Update the device device.mac_addr = device_json.get(DEVICE_MAC_ADDR_KEY).lower() device.manufacturer = device_json.get(DEVICE_MANUFACTURER_KEY) device.model = device_json.get(DEVICE_MODEL_KEY) + device.test_pack = device_json.get(DEVICE_TEST_PACK_KEY) + device.type = device_json.get(DEVICE_TYPE_KEY) + device.technology = device_json.get(DEVICE_TECH_KEY) + device.additional_info = device_json.get(DEVICE_ADDITIONAL_INFO_KEY) device.test_modules = device_json.get(DEVICE_TEST_MODULES_KEY) - self._test_run.save_device(device, device_json) + # Update device status to valid now that configuration is complete + device.status = "Valid" + + self._testrun.save_device(device) response.status_code = status.HTTP_200_OK return device.to_config_json() @@ -591,6 +684,12 @@ async def edit_device(self, request: Request, response: Response): async def get_report(self, response: Response, device_name, timestamp): device = self._session.get_device_by_name(device_name) + # If the device not found + if device is None: + LOGGER.info("Device not found, returning 404") + response.status_code = 404 + return self._generate_msg(False, "Device not found") + # 1.3 file path file_path = os.path.join( DEVICES_PATH, @@ -644,47 +743,77 @@ async def get_results(self, request: Request, response: Response, device_name, return self._generate_msg(False, "A device with that name could not be found") - file_path = self._get_test_run().get_test_orc().zip_results( + # Check if report exists (1.3 file path) + report_file_path = os.path.join( + DEVICES_PATH, + device_name, + "reports", + timestamp,"test", + device.mac_addr.replace(":","")) + + if not os.path.isdir(report_file_path): + # pre 1.3 file path + report_file_path = os.path.join(DEVICES_PATH, device_name, "reports", + timestamp) + + if not os.path.isdir(report_file_path): + LOGGER.info("Report could not be found, returning 404") + response.status_code = 404 + return self._generate_msg(False, "Report could not be found") + + zip_file_path = self._get_testrun().get_test_orc().zip_results( device, timestamp, profile) - if file_path is None: + if zip_file_path is None: response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR return self._generate_msg( False, "An error occurred whilst archiving test results") - if os.path.isfile(file_path): - return FileResponse(file_path) + if os.path.isfile(zip_file_path): + return FileResponse(zip_file_path) else: LOGGER.info("Test results could not be found, returning 404") response.status_code = 404 return self._generate_msg(False, "Test results could not be found") + async def get_devices_profile(self): + """Device profile questions""" + return self._device_profile + def _validate_device_json(self, json_obj): # Check all required properties are present - if not (DEVICE_MAC_ADDR_KEY in json_obj and DEVICE_MANUFACTURER_KEY - in json_obj and DEVICE_MODEL_KEY in json_obj): - return False + for string in [ + DEVICE_MAC_ADDR_KEY, + DEVICE_MANUFACTURER_KEY, + DEVICE_MODEL_KEY, + DEVICE_TYPE_KEY, + DEVICE_TECH_KEY, + DEVICE_ADDITIONAL_INFO_KEY + ]: + if string not in json_obj: + LOGGER.error(f"Missing required key {string} in device configuration") + return False # Check length of strings if len(json_obj.get(DEVICE_MANUFACTURER_KEY)) > 28 or len( json_obj.get(DEVICE_MODEL_KEY)) > 28: + LOGGER.error("Device manufacturer or model are longer than 28 characters") return False disallowed_chars = ["/", "\\", "\'", "\"", ";"] for char in json_obj.get(DEVICE_MANUFACTURER_KEY): if char in disallowed_chars: + LOGGER.error("Disallowed character in device manufacturer") return False for char in json_obj.get(DEVICE_MODEL_KEY): if char in disallowed_chars: + LOGGER.error("Disallowed character in device model") return False return True - def _get_test_run(self): - return self._test_run - # Profiles def get_profiles_format(self, response: Response): @@ -706,6 +835,12 @@ async def update_profile(self, request: Request, response: Response): LOGGER.debug("Received profile update request") + # Check if the profiles format was loaded correctly + if self.get_session().get_profiles_format() is None: + response.status_code = status.HTTP_501_NOT_IMPLEMENTED + return self._generate_msg(False, + "Risk profiles are not available right now") + try: req_raw = (await request.body()).decode("UTF-8") req_json = json.loads(req_raw) @@ -726,6 +861,7 @@ async def update_profile(self, request: Request, response: Response): profile = self.get_session().get_profile(profile_name) if profile is None: + # Create new profile profile = self.get_session().update_profile(req_json) @@ -886,7 +1022,13 @@ async def delete_cert(self, request: Request, response: Response): def get_test_modules(self): modules = [] - for module in self._test_run.get_test_orc().get_test_modules(): + for module in self._testrun.get_test_orc().get_test_modules(): if module.enabled and module.enable_container: modules.append(module.display_name) return modules + + def get_test_packs(self): + test_packs: list[str] = [] + for test_pack in self._testrun.get_test_orc().get_test_packs(): + test_packs.append(test_pack.name) + return test_packs diff --git a/framework/python/src/common/device.py b/framework/python/src/common/device.py index c6a289d2c..d90720d90 100644 --- a/framework/python/src/common/device.py +++ b/framework/python/src/common/device.py @@ -14,7 +14,7 @@ """Track device object information.""" -from typing import Dict, List +from typing import List, Dict from dataclasses import dataclass, field from common.testreport import TestReport from datetime import datetime @@ -23,17 +23,21 @@ class Device(): """Represents a physical device and it's configuration.""" + status: str = 'Valid' folder_url: str = None mac_addr: str = None manufacturer: str = None model: str = None + type: str = None + technology: str = None + test_pack: str = 'Device Qualification' + additional_info: List[dict] = field(default_factory=list) test_modules: Dict = field(default_factory=dict) ip_addr: str = None firmware: str = None device_folder: str = None reports: List[TestReport] = field(default_factory=list) max_device_reports: int = None - reports: List[TestReport] = field(default_factory=list) def add_report(self, report): self.reports.append(report) @@ -54,11 +58,18 @@ def to_dict(self): """Returns the device as a python dictionary. This is used for the system status API endpoint and in the report.""" device_json = {} + device_json['status'] = self.status device_json['mac_addr'] = self.mac_addr device_json['manufacturer'] = self.manufacturer device_json['model'] = self.model + device_json['type'] = self.type + device_json['technology'] = self.technology + device_json['test_pack'] = self.test_pack + device_json['additional_info'] = self.additional_info + if self.firmware is not None: device_json['firmware'] = self.firmware + device_json['test_modules'] = self.test_modules return device_json @@ -69,5 +80,9 @@ def to_config_json(self): device_json['mac_addr'] = self.mac_addr device_json['manufacturer'] = self.manufacturer device_json['model'] = self.model + device_json['type'] = self.type + device_json['technology'] = self.technology + device_json['test_pack'] = self.test_pack device_json['test_modules'] = self.test_modules + device_json['additional_info'] = self.additional_info return device_json diff --git a/framework/python/src/common/docker_util.py b/framework/python/src/common/docker_util.py new file mode 100644 index 000000000..06b030419 --- /dev/null +++ b/framework/python/src/common/docker_util.py @@ -0,0 +1,35 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Utility for common docker methods""" +import docker + +def create_private_net(network_name): + client = docker.from_env() + try: + network = client.networks.get(network_name) + network.remove() + except docker.errors.NotFound: + pass + + # TODO: These should be made into variables + ipam_pool = docker.types.IPAMPool(subnet='100.100.0.0/16', + iprange='100.100.100.0/24') + + ipam_config = docker.types.IPAMConfig(pool_configs=[ipam_pool]) + + client.networks.create(network_name, + ipam=ipam_config, + internal=True, + check_duplicate=True, + driver='macvlan') diff --git a/framework/python/src/common/mqtt.py b/framework/python/src/common/mqtt.py index c58d24d3f..fc0458e7d 100644 --- a/framework/python/src/common/mqtt.py +++ b/framework/python/src/common/mqtt.py @@ -28,26 +28,25 @@ def __init__(self, message: str) -> None: class MQTT: - """ MQTT client class - """ + """ MQTT client class""" def __init__(self) -> None: self._host = WEBSOCKETS_HOST self._client = mqtt_client.Client(mqtt_client.CallbackAPIVersion.VERSION2) - LOGGER.setLevel(logger.logging.INFO) self._client.enable_logger(LOGGER) + LOGGER.setLevel(logger.logging.INFO) def _connect(self): - """Establish connection to Mosquitto server - - Raises: - MQTTException: Raises exception on connection error - """ + """Establish connection to MQTT broker""" if not self._client.is_connected(): try: self._client.connect(self._host, WEBSOCKETS_PORT, 60) - except (ValueError, ConnectionRefusedError) as e: - LOGGER.error("Can't connect to host") - raise MQTTException("Connection to the Mosquitto server failed") from e + except (ValueError, ConnectionRefusedError): + LOGGER.error("Cannot connect to MQTT broker") + + def disconnect(self): + """Disconnect the local client from the MQTT broker""" + if self._client.is_connected(): + self._client.disconnect() def send_message(self, topic: str, message: t.Union[str, dict]) -> None: """Send message to specific topic diff --git a/framework/python/src/common/risk_profile.py b/framework/python/src/common/risk_profile.py index f50dffdde..eeae44db7 100644 --- a/framework/python/src/common/risk_profile.py +++ b/framework/python/src/common/risk_profile.py @@ -20,10 +20,15 @@ from common import logger import json import os +from jinja2 import Template +from copy import deepcopy PROFILES_PATH = 'local/risk_profiles' LOGGER = logger.get_logger('risk_profile') RESOURCES_DIR = 'resources/report' +TEMPLATE_FILE = 'risk_report_template.html' +TEMPLATE_STYLES = 'risk_report_styles.css' +DEVICE_FORMAT_PATH = 'resources/devices/device_profile.json' # Locate parent directory current_dir = os.path.dirname(os.path.realpath(__file__)) @@ -43,6 +48,34 @@ class RiskProfile(): def __init__(self, profile_json=None, profile_format=None): + # Jinja template + with open(os.path.join(report_resource_dir, TEMPLATE_FILE), + 'r', + encoding='UTF-8' + ) as template_file: + self._template = Template(template_file.read()) + with open(os.path.join(report_resource_dir, + TEMPLATE_STYLES), + 'r', + encoding='UTF-8' + ) as style_file: + self._template_styles = style_file.read() + + # Device profile format + self._device_format = [] + try: + with open(os.path.join(root_dir, DEVICE_FORMAT_PATH), + 'r', + encoding='utf-8') as device_format_file: + device_format_json = json.load(device_format_file) + for step in device_format_json: + self._device_format.extend(step['questions']) + except (IOError, ValueError) as e: + LOGGER.error( + 'An error occurred whilst loading the device profile format') + LOGGER.debug(e) + + if profile_json is None or profile_format is None: return @@ -92,6 +125,7 @@ def update(self, profile_json, profile_format): self.risk = new_profile.risk def get_file_path(self): + """Returns the file path for the current risk profile json""" return os.path.join(PROFILES_PATH, self.name + '.json') @@ -108,6 +142,7 @@ def _validate(self, profile_json, profile_format): self.status = 'Draft' def update_risk(self, profile_format): + """Update the calculated risk for the risk profile""" if self.status == 'Valid': @@ -176,6 +211,15 @@ def update_risk(self, profile_format): self.risk = risk + def _update_risk_by_device(self): + risk = self.risk + if self._device and self.status == 'Valid': + for question in self._device.additional_info: + if 'risk' in question and question['risk'] == 'High': + risk = 'High' + break + return risk + def _get_format_question(self, question: str, profile_format: dict): for q in profile_format: @@ -281,6 +325,7 @@ def _expired(self): return today > expiry_date def to_json(self, pretty=False): + """Returns the current risk profile in JSON format""" json_dict = { 'name': self.name, 'version': self.version, @@ -293,386 +338,88 @@ def to_json(self, pretty=False): return json.dumps(json_dict, indent=indent) def to_html(self, device): - - self._device = device - - return f''' - - - {self._generate_head()} - -

- - - ''' - - def _generate_head(self): - - return f''' - - - - Risk Assessment - - - ''' - - def _generate_header(self): + """Returns the current risk profile in HTML format""" + + high_risk_message = '''The device has been assessed to be high + risk due to the nature of the answers provided + about the device functionality.''' + limited_risk_message = '''The device has been assessed to be limited risk + due to the nature of the answers provided about + the device functionality.''' with open(test_run_img_file, 'rb') as f: - tr_img_b64 = base64.b64encode(f.read()).decode('utf-8') - header = f''' -
-

Risk assessment

-

- {self._device.manufacturer} - {self._device.model} -

''' - header += f'''Testrun -
- ''' - return header - - def _generate_risk_banner(self): - return f''' -
-
-

{'high' if self.risk == 'High' else 'limited'} Risk

-
-
- { - 'The device has been assessed to be high risk due to the nature of the answers provided about the device functionality.' - if self.risk == 'High' else - 'The device has been assessed to be limited risk due to the nature of the answers provided about the device functionality.' - } -
-
- ''' - - def _generate_risk_questions(self): - + logo_img_b64 = base64.b64encode(f.read()).decode('utf-8') + + self._device = self._format_device_profile(device) + pages = self._generate_report_pages() + return self._template.render( + styles=self._template_styles, + manufacturer=self._device.manufacturer, + model=self._device.model, + logo=logo_img_b64, + risk=self._update_risk_by_device(), + high_risk_message=high_risk_message, + limited_risk_message=limited_risk_message, + pages=pages, + total_pages=len(pages), + version=self.version, + created_at=self.created.strftime('%d.%m.%Y') + ) + + def _generate_report_pages(self): max_page_height = 350 - content = '' - - content += self._generate_table_head() - - index = 1 height = 0 + pages = [] + current_page = [] + index = 1 + + questions = deepcopy(self._device.additional_info) + questions.extend(self.questions) - for question in self.questions: + for question in questions: if height > max_page_height: - content += self._generate_new_page() + pages.append(current_page) height = 0 + current_page = [] - content += f''' -
-
{index}.
-
{question['question']}
-
''' + page_item = deepcopy(question) - # String answers (one line) - if isinstance(question['answer'], str): - content += question['answer'] + if isinstance(page_item['answer'], str): - if len(question['answer']) > 400: + if len(page_item['answer']) > 400: height += 160 - elif len(question['answer']) > 300: + elif len(page_item['answer']) > 300: height += 140 - elif len(question['answer']) > 200: + elif len(page_item['answer']) > 200: height += 120 - elif len(question['answer']) > 100: + elif len(page_item['answer']) > 100: height += 70 else: height += 53 # Select multiple answers - elif isinstance(question['answer'], list): - content += '
    ' + elif isinstance(page_item['answer'], list): + text_answers = [] options = self._get_format_question( - question=question['question'], + question=page_item['question'], profile_format=self._profile_format)['options'] - for answer_index in question['answer']: - height += 40 - content += f''' -
  • - {self._get_option_from_index(options, answer_index)['text']}
  • ''' - - content += '
' - - # Question risk label - if 'risk' in question: - if question['risk'] == 'High': - content += '
HIGH RISK
' - elif question['risk'] == 'Limited': - content += '''
- LIMITED RISK
''' - - content += '''
''' + options_dict = dict(enumerate(options)) + for answer_index in page_item['answer']: + height += 40 + text_answers.append(options_dict[answer_index]['text']) + page_item['answer'] = text_answers + page_item['index'] = index index += 1 + current_page.append(page_item) + pages.append(current_page) - return content - - def _generate_table_head(self): - return ''' -
-
-
Question
-
Answer
-
''' - - def _generate_new_page(self): - - # End the current table - content = ''' -
''' - - # End the page - content += self._generate_footer() - content += '

J*V<(k-r@DT^pndo5E0+!^Fos-0~B+VA` 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 delta 142395 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

' - - # Start a new page - content += ''' -
- ''' - - content += self._generate_header() - - content += self._generate_table_head() - - return content - - def _generate_footer(self): - footer = f''' - - ''' - return footer - - def _generate_css(self): - return ''' - /* Set some global variables */ - :root { - --header-height: .75in; - --header-width: 8.5in; - --header-pos-x: 0in; - --header-pos-y: 0in; - --page-width: 8.5in; - } - - @font-face { - font-family: 'Google Sans'; - font-style: normal; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJllpyk.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; - } - - @font-face { - font-family: 'Roboto Mono'; - font-style: normal; - src: url(https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; - } - - /* Define some common body formatting*/ - body { - font-family: 'Google Sans', sans-serif; - margin: 0px; - padding: 0px; - } - - /* Sets proper page size during print to pdf for weasyprint */ - @page { - size: Letter; - width: 8.5in; - height: 11in; - } - - .page { - position: relative; - margin: 0 20px; - width: 8.5in; - height: 11in; - } - - /* Define the header related css elements*/ - .header { - position: relative; - } - - h1 { - margin: 0 0 8px 0; - font-size: 20px; - font-weight: 400; - } - - h2 { - margin: 0px; - font-size: 48px; - font-weight: 700; - } - - h3 { - font-size: 24px; - margin-bottom: 10px; - margin-top: 15px; - } - - h4 { - font-size: 12px; - font-weight: 500; - color: #5F6368; - margin-bottom: 0; - margin-top: 0; - } - - /* CSS for the footer */ - .footer { - position: absolute; - height: 30px; - width: 8.5in; - bottom: 0in; - border-top: 1px solid #D3D3D3; - } - - .footer-label { - color: #3C4043; - position: absolute; - top: 5px; - font-size: 12px; - } - - @media print { - @page { - size: Letter; - width: 8.5in; - height: 11in; - } - } - - .risk-banner { - min-height: 120px; - padding: 5px 40px 0 40px; - margin-top: 30px; - } - - .risk-banner-limited { - background-color: #E4F7FB; - color: #007B83; - } - - .risk-banner-high { - background-color: #FCE8E6; - color: #C5221F; - } - - .risk-banner-title { - text-transform: uppercase; - font-weight: bold; - } - - .risk-table { - width: 100%; - margin-top: 40px; - text-align: left; - color: #3C4043; - font-size: 14px; - } - - .risk-table-head { - margin-bottom: 15px; - } - - .risk-table-head-question { - display: inline-block; - margin-left: 70px; - font-weight: bold; - } - - .risk-table-head-answer { - display: inline-block; - margin-left: 325px; - font-weight: bold; - } - - .risk-table-row { - margin-bottom: 8px; - background-color: #F8F9FA; - display: flex; - align-items: stretch; - overflow: hidden; - } - - .risk-question-no { - padding: 15px 20px; - width: 10px; - display: inline-block; - vertical-align: top; - position: relative; - } - - .risk-question { - padding: 15px 20px; - display: inline-block; - width: 350px; - vertical-align: top; - position: relative; - height: 100%; - } - - .risk-answer { - background-color: #E8F0FE; - padding: 15px 20px; - display: inline-block; - width: 340px; - position: relative; - height: 100%; - } - - ul { - margin-top: 0; - } - - .risk-label{ - position: absolute; - top: 0px; - right: 0px; - width: 52px; - height: 16px; - font-family: 'Google Sans', sans-serif; - font-size: 8px; - font-weight: 500; - line-height: 16px; - letter-spacing: 0.64px; - text-align: center; - font-weight: bold; - border-radius: 3px; - } - - .risk-label-high{ - background-color: #FCE8E6; - color: #C5221F; - } - - .risk-label-limited{ - width: 65px; - background-color:#E4F7FB; - color: #007B83; - } - ''' + return pages def to_pdf(self, device): + """Returns the current risk profile in PDF format""" # Resolve the data as html first html = self.to_html(device) @@ -681,3 +428,21 @@ def to_pdf(self, device): pdf_bytes = BytesIO() HTML(string=html).write_pdf(pdf_bytes) return pdf_bytes + + # Adding risks to device profile questions + def _format_device_profile(self, device): + device_copy = deepcopy(device) + risk_map = { + question['question']: { + option['text']: option.get('risk', None) + for option in question['options'] if 'risk' in option + } + for question in self._device_format + } + for question in device_copy.additional_info: + risk = risk_map.get( + question['question'], {} + ).get(question['answer'], None) + if risk: + question['risk'] = risk + return device_copy diff --git a/framework/python/src/common/statuses.py b/framework/python/src/common/statuses.py new file mode 100644 index 000000000..4817d7cf8 --- /dev/null +++ b/framework/python/src/common/statuses.py @@ -0,0 +1,36 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Enums for Testrun""" + + +class TestrunStatus: + IDLE = "Idle" + WAITING_FOR_DEVICE = "Waiting for Device" + MONITORING = "Monitoring" + IN_PROGRESS = "In Progress" + CANCELLED = "Cancelled" + COMPLIANT = "Compliant" + NON_COMPLIANT = "Non-Compliant" + STOPPING = "Stopping" + + +class TestResult: + IN_PROGRESS = "In Progress" + COMPLIANT = "Compliant" + NON_COMPLIANT = "Non-Compliant" + ERROR = "Error" + FEATURE_NOT_DETECTED = "Feature Not Detected" + INFORMATIONAL = "Informational" + NOT_STARTED = "Not Started" + DISABLED = "Disabled" diff --git a/framework/python/src/common/testreport.py b/framework/python/src/common/testreport.py index 88a25a2b1..f9401fe80 100644 --- a/framework/python/src/common/testreport.py +++ b/framework/python/src/common/testreport.py @@ -17,14 +17,19 @@ from weasyprint import HTML from io import BytesIO from common import util +from common.statuses import TestrunStatus import base64 import os from test_orc.test_case import TestCase +from jinja2 import Environment, FileSystemLoader +from collections import OrderedDict DATE_TIME_FORMAT = '%Y-%m-%d %H:%M:%S' RESOURCES_DIR = 'resources/report' TESTS_FIRST_PAGE = 11 TESTS_PER_PAGE = 20 +TEST_REPORT_STYLES = 'test_report_styles.css' +TEST_REPORT_TEMPLATE = 'test_report_template.html' # Locate parent directory current_dir = os.path.dirname(os.path.realpath(__file__)) @@ -37,13 +42,15 @@ report_resource_dir = os.path.join(root_dir, RESOURCES_DIR) test_run_img_file = os.path.join(report_resource_dir, 'testrun.png') +qualification_icon = os.path.join(report_resource_dir, 'qualification-icon.png') +pilot_icon = os.path.join(report_resource_dir, 'pilot-icon.png') class TestReport(): """Represents a previous Testrun report.""" def __init__(self, - status='Non-Compliant', + status=TestrunStatus.NON_COMPLIANT, started=None, finished=None, total_tests=0): @@ -115,6 +122,10 @@ def to_json(self): if test.recommendations is not None and len(test.recommendations) > 0: test_dict['recommendations'] = test.recommendations + if (test.optional_recommendations is not None + and len(test.optional_recommendations) > 0): + test_dict['optional_recommendations'] = test.optional_recommendations + test_results.append(test_dict) report_json['tests'] = {'total': self._total_tests, @@ -141,6 +152,12 @@ def from_json(self, json_file): if 'test_modules' in json_file['device']: self._device['test_modules'] = json_file['device']['test_modules'] + if 'test_pack' in json_file['device']: + self._device['test_pack'] = json_file['device']['test_pack'] + + if 'additional_info' in json_file['device']: + self._device['device_profile'] = json_file['device']['additional_info'] + self._status = json_file['status'] self._started = datetime.strptime(json_file['started'], DATE_TIME_FORMAT) self._finished = datetime.strptime(json_file['finished'], DATE_TIME_FORMAT) @@ -157,8 +174,16 @@ def from_json(self, json_file): expected_behavior=test_result['expected_behavior'], required_result=test_result['required_result'], result=test_result['result']) + + # Add test recommendations if 'recommendations' in test_result: test_case.recommendations = test_result['recommendations'] + + # Add optional test recommendations + if 'optional_recommendations' in test_result: + test_case.optional_recommendations = test_result[ + 'optional_recommendations'] + self.add_test(test_case) # Create a pdf file in memory and return the bytes @@ -172,35 +197,81 @@ def to_pdf(self): return pdf_bytes def to_html(self): - json_data = self.to_json() - return f''' - - - {self.generate_head()} - - {self.generate_body(json_data)} - - - ''' - - def generate_test_sections(self, json_data): - results = json_data['tests']['results'] - sections = '' - for result in results: - sections += self.generate_test_section(result) - return sections - - def generate_test_section(self, result): - section_content = '
\n' - for key, value in result.items(): - if value is not None: # Check if the value is not None - # Replace underscores and capitalize - formatted_key = key.replace('_', ' ').title() - section_content += f'

{formatted_key}: {value}

\n' - section_content += '
\n
\n' - return section_content - - def generate_pages(self, json_data): + + # Jinja template + template_env = Environment(loader=FileSystemLoader(report_resource_dir)) + template = template_env.get_template(TEST_REPORT_TEMPLATE) + with open(os.path.join(report_resource_dir, + TEST_REPORT_STYLES), + 'r', + encoding='UTF-8' + ) as style_file: + styles = style_file.read() + + # Load Testrun logo to base64 + with open(test_run_img_file, 'rb') as f: + logo = base64.b64encode(f.read()).decode('utf-8') + + json_data=self.to_json() + + # Icons + with open(qualification_icon, 'rb') as f: + icon_qualification = base64.b64encode(f.read()).decode('utf-8') + with open(pilot_icon, 'rb') as f: + icon_pilot = base64.b64encode(f.read()).decode('utf-8') + + # Convert the timestamp strings to datetime objects + start_time = datetime.strptime(json_data['started'], '%Y-%m-%d %H:%M:%S') + end_time = datetime.strptime(json_data['finished'], '%Y-%m-%d %H:%M:%S') + + # Calculate the duration + duration = end_time - start_time + + # Calculate number of successful tests + successful_tests = 0 + for test in json_data['tests']['results']: + if test['result'] != 'Error': + successful_tests += 1 + + # Obtain the steps to resolve + steps_to_resolve = self._get_steps_to_resolve(json_data) + + # Obtain optional recommendations + optional_steps_to_resolve = self._get_optional_steps_to_resolve(json_data) + + module_reports = self._get_module_pages() + pages_num = self._pages_num(json_data) + total_pages = pages_num + len(module_reports) + 1 + if len(steps_to_resolve) > 0: + total_pages += 1 + if (len(optional_steps_to_resolve) > 0 + and json_data['device']['test_pack'] == 'Pilot Assessment' + ): + total_pages += 1 + + return template.render(styles=styles, + logo=logo, + icon_qualification=icon_qualification, + icon_pilot=icon_pilot, + version=self._version, + json_data=json_data, + device=json_data['device'], + modules=self._device_modules(json_data['device']), + test_status=json_data['status'], + duration=duration, + successful_tests=successful_tests, + total_tests=self._total_tests, + test_results=json_data['tests']['results'], + steps_to_resolve=steps_to_resolve, + optional_steps_to_resolve=optional_steps_to_resolve, + module_reports=module_reports, + pages_num=pages_num, + total_pages=total_pages, + tests_first_page=TESTS_FIRST_PAGE, + tests_per_page=TESTS_PER_PAGE, + ) + + def _pages_num(self, json_data): # Calculate pages test_count = len(json_data['tests']['results']) @@ -208,136 +279,62 @@ def generate_pages(self, json_data): # Multiple pages required if test_count > TESTS_FIRST_PAGE: # First page - full_page = 1 + pages = 1 - # Remaining tests + # Remaining testsgenerate test_count -= TESTS_FIRST_PAGE - full_page += (int)(test_count / TESTS_PER_PAGE) - partial_page = 1 if test_count % TESTS_PER_PAGE > 0 else 0 + pages += (int)(test_count / TESTS_PER_PAGE) + pages = pages + 1 if test_count % TESTS_PER_PAGE > 0 else pages # 1 page required - elif test_count == TESTS_FIRST_PAGE: - full_page = 1 - partial_page = 0 - # Less than 1 page required else: - full_page = 0 - partial_page = 1 + pages = 1 - num_pages = full_page + partial_page - - pages = '' - for _ in range(num_pages): - self._cur_page += 1 - pages += self.generate_results_page(json_data=json_data, - page_num=self._cur_page) return pages - def generate_results_page(self, json_data, page_num): - page = '
' - page += self.generate_header(json_data, (page_num == 1)) - if page_num == 1: - page += self.generate_summary(json_data) - page += self.generate_results(json_data, page_num) - page += self.generate_footer(page_num) - page += '
' - page += '
' - return page - - def generate_module_page(self, json_data, module_report): - self._cur_page += 1 - page = '
' - page += self.generate_header(json_data, False) - page += f''' -
- {module_report} -
''' - page += self.generate_footer(self._cur_page) - page += '
' # Page end - page += '
' - return page - - def generate_steps_to_resolve(self, json_data): - - steps_so_far = 0 + def _device_modules(self, device): + sorted_modules = {} + + if 'test_modules' in device: + + for test_module in device['test_modules']: + if 'enabled' in device['test_modules'][test_module]: + sorted_modules[ + util.get_module_display_name(test_module)] = device['test_modules'][ + test_module]['enabled'] + + # Sort the modules by enabled first + sorted_modules = OrderedDict(sorted(sorted_modules.items(), + key=lambda x:x[1], + reverse=True) + ) + return sorted_modules + + def _get_steps_to_resolve(self, json_data): tests_with_recommendations = [] - index = 1 # Collect all tests with recommendations for test in json_data['tests']['results']: if 'recommendations' in test: tests_with_recommendations.append(test) - # Check if test has recommendations - if len(tests_with_recommendations) == 0: - return '' - - # Start new page - self._cur_page += 1 - page = '
' - page += self.generate_header(json_data, False) - - # Add title - page += '

Steps to Resolve

' - - for test in tests_with_recommendations: - - # Generate new page - if steps_so_far == 4 and ( - len(tests_with_recommendations) - (index-1) > 0): - - # Reset steps counter - steps_so_far = 0 - - # Render footer - page += self.generate_footer(self._cur_page) - page += '
' # Page end - page += '
' - - # Render new header - self._cur_page += 1 - page += '
' - page += self.generate_header(json_data, False) - - # Render test recommendations - page += f''' -
-
- {index}. -
- Name
{test["name"]} -
-
- Description
{test["description"]} -
-
-
- Steps to resolve - ''' - - step_number = 1 - for recommendation in test['recommendations']: - page += f''' -
{ - step_number}. {recommendation}''' - step_number += 1 - - page += '
' - - index += 1 - steps_so_far += 1 - - # Render final footer - page += self.generate_footer(self._cur_page) - page += '
' # Page end - page += '
' - - return page - - def generate_module_pages(self, json_data): - pages = '' + return tests_with_recommendations + + def _get_optional_steps_to_resolve(self, json_data): + tests_with_recommendations = [] + + # Collect all tests with recommendations + for test in json_data['tests']['results']: + if 'optional_recommendations' in test: + tests_with_recommendations.append(test) + + return tests_with_recommendations + + def _get_module_pages(self): content_max_size = 913 + reports = [] + for module_reports in self._module_reports: # ToDo: Figure out how to make this dynamic # Padding values from CSS @@ -391,8 +388,7 @@ def generate_module_pages(self, json_data): # If in the middle of a table, close the table if data_rows_active: page_content += '' - page = self.generate_module_page(json_data, page_content) - pages += page + '\n' + reports.append(page_content) content_size = 0 # If in the middle of a data table, restart # it for the rest of the rows @@ -400,727 +396,5 @@ def generate_module_pages(self, json_data): if data_rows_active else '') page_content += line + '\n' if len(page_content) > 0: - page = self.generate_module_page(json_data, page_content) - pages += page + '\n' - return pages - - def generate_body(self, json_data): - self._num_pages = 0 - self._cur_page = 0 - body = f''' - - {self.generate_pages(json_data)} - {self.generate_steps_to_resolve(json_data)} - {self.generate_module_pages(json_data)} - - ''' - # Set the max pages after all pages have been generated - return body.replace('MAX_PAGE', str(self._cur_page)) - - def generate_footer(self, page_num): - footer = f''' - - ''' - return footer - - def generate_results(self, json_data, page_num): - - successful_tests = 0 - for test in json_data['tests']['results']: - if test['result'] != 'Error': - successful_tests += 1 - - result_list = f''' -
-

Results List ({successful_tests}/{self._total_tests})

-
-
Name
-
Description
-
Result
-
''' - if page_num == 1: - start = 0 - elif page_num == 2: - start = TESTS_FIRST_PAGE - else: - start = (page_num - 2) * TESTS_PER_PAGE + TESTS_FIRST_PAGE - results_on_page = TESTS_FIRST_PAGE if page_num == 1 else TESTS_PER_PAGE - result_end = min(start + results_on_page, - len(json_data['tests']['results'])) - for ix in range(result_end - start): - result = json_data['tests']['results'][ix + start] - result_list += self.generate_result(result) - result_list += '
' - return result_list - - def generate_result(self, result): - if result['result'] == 'Non-Compliant': - result_class = 'result-test-result-non-compliant' - elif result['result'] == 'Compliant': - result_class = 'result-test-result-compliant' - elif result['result'] == 'Error': - result_class = 'result-test-result-error' - elif result['result'] == 'Feature Not Detected': - result_class = 'result-test-result-feature-not-detected' - elif result['result'] == 'Informational': - result_class = 'result-test-result-informational' - else: - result_class = 'result-test-result-skipped' - - result_html = f''' -
-
{result['name']}
-
{result['description']}
-
{result['result']}
-
- ''' - return result_html - - def generate_header(self, json_data, first_page): - with open(test_run_img_file, 'rb') as f: - tr_img_b64 = base64.b64encode(f.read()).decode('utf-8') - header = '' - - if first_page: - header += f''' -
-

Testrun report

-

- {json_data["device"]["manufacturer"]} - {json_data["device"]["model"]} -

''' - else: - header += f''' -
-

Testrun report

-

- {json_data["device"]["manufacturer"]} - {json_data["device"]["model"]} -

''' - header += f'''Testrun -
- ''' - return header - - def generate_summary(self, json_data): - # Generate the basic content section layout - summary = ''' -
- ''' - # Add the device information - manufacturer = (json_data['device']['manufacturer'] - if 'manufacturer' in json_data['device'] else 'Undefined') - model = (json_data['device']['model'] - if 'model' in json_data['device'] else 'Undefined') - fw = (json_data['device']['firmware'] - if 'firmware' in json_data['device'] else 'Undefined') - mac = (json_data['device']['mac_addr'] - if 'mac_addr' in json_data['device'] else 'Undefined') - - summary += '''
-
''' - - summary += self.generate_device_summary_label('Manufacturer', manufacturer) - summary += self.generate_device_summary_label('Model', model) - summary += self.generate_device_summary_label('Firmware', fw) - summary += self.generate_device_summary_label('MAC Address', - mac, - trailing_space=False) - - summary += '
' - - # Add device configuration - summary += ''' -
-
-

Device Configuration

-
- ''' - - if 'test_modules' in json_data['device']: - - sorted_modules = {} - - for test_module in json_data['device']['test_modules']: - if 'enabled' in json_data['device']['test_modules'][test_module]: - sorted_modules[test_module] = json_data['device']['test_modules'][ - test_module]['enabled'] - - # Sort the modules by enabled first - sorted_modules = sorted(sorted_modules.items(), - key=lambda x:x[1], - reverse=True) - - for module in sorted_modules: - summary += self.generate_device_module_label( - module[0], - module[1] - ) - - summary += '
' - - # Add device configuration - summary += ''' -
-
-

Device Configuration

-
- ''' - - if 'test_modules' in json_data['device']: - - sorted_modules = {} - - for test_module in json_data['device']['test_modules']: - if 'enabled' in json_data['device']['test_modules'][test_module]: - sorted_modules[test_module] = json_data['device']['test_modules'][ - test_module]['enabled'] - - # Sort the modules by enabled first - sorted_modules = sorted(sorted_modules.items(), - key=lambda x:x[1], - reverse=True) - - for module in sorted_modules: - summary += self.generate_device_module_label( - module[0], - module[1] - ) - - summary += '
' - - # Add the result summary - summary += self.generate_result_summary(json_data) - - summary += '\n
' - return summary - - def generate_device_module_label(self, module, enabled): - - # Do not render deleted modules - if module == 'nmap': - return '' - - label = '
' - if enabled: - label += '' - else: - label += '' - label += util.get_module_display_name(module) - label += '
' - return label - - def generate_result_summary(self, json_data): - if json_data['status'] == 'Compliant': - result_summary = '''
''' - else: - result_summary = '''
''' - result_summary += self.generate_result_summary_item('Test status', - 'Complete') - result_summary += self.generate_result_summary_item( - 'Test result', - json_data['status'], - style='color: white; font-size:24px; font-weight: 700;') - result_summary += self.generate_result_summary_item('Started', - json_data['started']) - - # Convert the timestamp strings to datetime objects - start_time = datetime.strptime(json_data['started'], '%Y-%m-%d %H:%M:%S') - end_time = datetime.strptime(json_data['finished'], '%Y-%m-%d %H:%M:%S') - # Calculate the duration - duration = end_time - start_time - result_summary += self.generate_result_summary_item('Duration', - str(duration)) - - result_summary += '\n
' - return result_summary - - def generate_result_summary_item(self, key, value, style=None): - summary_item = f'''
{key}
''' - if style is not None: - summary_item += f'''
{value}
''' - else: - summary_item += f'''
{value}
''' - return summary_item - - def generate_device_summary_label(self, key, value, trailing_space=True): - label = f''' -

{key}

-
{value}
- ''' - if trailing_space: - label += '''
''' - return label - - def generate_head(self): - return f''' - - - - Testrun Report - - - ''' - - def generate_css(self): - return ''' - /* Set some global variables */ - :root { - --header-height: .75in; - --header-width: 8.5in; - --header-pos-x: 0in; - --header-pos-y: 0in; - --page-width: 8.5in; - --summary-height: 2.8in; - --vertical-line-height: calc(var(--summary-height)-.2in); - --vertical-line-pos-x: 25%; - } - - @font-face { - font-family: 'Google Sans'; - font-style: normal; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJllpyk.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; - } - - @font-face { - font-family: 'Roboto Mono'; - font-style: normal; - src: url(https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; - } - - /* Define some common body formatting*/ - body { - font-family: 'Google Sans', sans-serif; - margin: 0px; - padding: 0px; - } - - /* Use this for various section breaks*/ - .gradient-line { - position: relative; - background-image: linear-gradient(to right, red, blue, green, yellow, orange); - height: 1px; - /* Adjust the height as needed */ - width: 100%; - /* To span the entire width */ - display: block; - /* Ensures it's a block-level element */ - } - - /* Sets proper page size during print to pdf for weasyprint */ - @page { - size: Letter; - width: 8.5in; - height: 11in; - } - - .page { - position: relative; - margin: 0 20px; - width: 8.5in; - height: 11in; - } - - /* Define the header related css elements*/ - .header { - position: relative; - } - - h1 { - margin: 0 0 8px 0; - font-size: 20px; - font-weight: 400; - } - - h2 { - margin: 0px; - font-size: 48px; - font-weight: 700; - } - - h3 { - font-size: 24px; - } - - h4 { - font-size: 12px; - font-weight: 500; - color: #5F6368; - margin-bottom: 0; - margin-top: 0; - } - - .module-summary { - background-color: #F8F9FA; - width: 100%; - margin-bottom: 25px; - margin-top: 25px; - } - - .module-summary thead tr th { - text-align: left; - padding-top: 15px; - padding-left: 15px; - font-weight: 500; - color: #5F6368; - font-size: 14px; - } - - .module-summary tbody tr td { - padding-bottom: 15px; - padding-left: 15px; - font-size: 24px; - } - - .module-data { - border: 1px solid #DADCE0; - border-radius: 3px; - border-spacing: 0; - } - - .module-data thead tr th { - text-align: left; - padding: 12px 25px; - color: #3C4043; - font-size: 14px; - font-weight: 700; - } - - .module-data tbody tr td { - text-align: left; - padding: 12px 25px; - color: #3C4043; - font-size: 14px; - font-weight: 400; - border-top: 1px solid #DADCE0; - font-family: 'Roboto Mono', monospace; - } - - div.steps-to-resolve { - background-color: #F8F9FA; - margin-bottom: 30px; - width: 756px; - padding: 20px 30px; - vertical-align: top; - } - - .steps-to-resolve-row { - vertical-align: top; - } - - .steps-to-resolve-test-name { - display: inline-block; - margin-left: 70px; - margin-bottom: 20px; - width: 250px; - vertical-align: top; - } - - .steps-to-resolve-description { - display: inline-block; - } - - .steps-to-resolve.subtitle { - text-align: left; - padding-top: 15px; - font-weight: 500; - color: #5F6368; - font-size: 14px; - } - - .steps-to-resolve-index { - font-size: 40px; - position: absolute; - } - - .callout-container.info { - background-color: #e8f0fe; - } - - .callout-container.info .icon { - width: 22px; - height: 22px; - margin-right: 5px; - background-size: contain; - background-image: url('data:image/png;base64,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'); - } - - .callout-container { - display: flex; - box-sizing: border-box; - height: auto; - min-height: 48px; - padding: 6px 24px; - border-radius: 8px; - align-items: center; - gap: 10px; - color: #3c4043; - font-size: 14px; - } - - .device-information { - padding-top: 0.2in; - padding-left: 0.2in; - background-color: #F8F9FA; - width: 250px; - height: 100.4%; - } - - /* Define the summary related css elements*/ - .summary-content { - position: relative; - width: var(--page-width); - height: var(--summary-height); - margin-top: 19px; - margin-bottom: 19px; - background-color: #E8EAED; - padding-bottom: 20px; - } - - .summary-item-label { - position: relative; - } - - .summary-item-value { - position: relative; - font-size: 20px; - font-weight: 400; - color: #202124; - } - - .summary-item-space { - position: relative; - padding-bottom: 15px; - margin: 0; - } - - .summary-device-modules { - position: absolute; - left: 3.2in; - top: .3in; - } - - .summary-device-module-label { - font-size: 16px; - font-weight: 500; - color: #202124; - width: fit-content; - margin-bottom: 0.1in; - } - - .summary-vertical-line { - width: 1px; - height: var(--vertical-line-height); - background-color: #80868B; - position: absolute; - top: .3in; - bottom: .1in; - left: 3in; - } - - /* CSS for the color box */ - .summary-color-box { - position: absolute; - right: 0in; - top: 0in; - width: 2.6in; - height: 100%; - } - - .summary-box-compliant { - background-color: rgb(24, 128, 56); - } - - .summary-box-non-compliant { - background-color: #b31412; - } - - .summary-box-label { - font-size: 14px; - margin-top: 5px; - color: #DADCE0; - position: relative; - top: 10px; - left: 20px; - font-weight: 500; - } - - .summary-box-value { - font-size: 18px; - margin: 0 0 10px 0; - color: #ffffff; - position: relative; - top: 10px; - left: 20px; - } - - .result-list-title { - font-size: 24px; - } - - .result-list { - position: relative; - margin-top: .2in; - font-size: 18px; - } - - .result-line { - border: 1px solid #D3D3D3; - /* Light Gray border*/ - height: .4in; - width: 8.5in; - } - - .result-line-result { - border-top: 0px; - } - - .result-list-header-label { - font-weight: 500; - position: absolute; - font-size: 12px; - font-weight: bold; - height: 40px; - display: flex; - align-items: center; - } - - .result-test-label { - position: absolute; - font-size: 12px; - margin-top: 12px; - max-width: 300px; - font-weight: normal; - align-items: center; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - } - - .result-test-description { - max-width: 380px; - } - - .result-test-result-error { - background-color: #FCE8E6; - color: #C5221F; - left: 7.3in; - } - - .result-test-result-feature-not-detected { - background-color: #e3e3e3; - left: 6.85in; - } - - .result-test-result-informational { - background-color: #d9f0ff; - color: #0b5c8d; - left: 7.08in; - } - - .result-test-result-non-compliant { - background-color: #FCE8E6; - color: #C5221F; - left: 7.01in; - } - - .result-test-result { - position: absolute; - font-size: 12px; - width: fit-content; - height: 12px; - margin-top: 8px; - padding: 4px 4px 7px 5px; - border-radius: 2px; - } - - .result-test-result-compliant { - background-color: #E6F4EA; - color: #137333; - left: 7.16in; - } - - .result-test-result-skipped { - background-color: #e3e3e3; - color: #393939; - left: 7.24in; - } - - /* CSS for the footer */ - .footer { - position: absolute; - height: 30px; - width: 8.5in; - bottom: 0in; - border-top: 1px solid #D3D3D3; - } - - .footer-label { - color: #3C4043; - position: absolute; - top: 5px; - font-size: 12px; - } - - /*CSS for the markdown tables */ - .markdown-table { - border-collapse: collapse; - margin-left: 20px; - background-color: #F8F9FA; - } - - .markdown-table th, .markdown-table td { - border: none; - text-align: left; - padding: 8px; - } - - .markdown-header-h1 { - margin-top:20px; - margin-bottom:20px; - margin-right:0px; - font-size: 2em; - } - - .markdown-header-h2 { - margin-top:20px; - margin-bottom:20px; - margin-right:0px; - font-size: 1.5em; - } - - .module-page-content { - /*Page height minus header(93px), footer(30px), - and a 20px bottom padding.*/ - height: calc(11in - 93px - 30px - 20px); - - /* In case we mess something up in our calculations - we'll cut off the content of the page so - the header, footer and line break work - as expected - */ - overflow: hidden; - } - - .module-page-content h1 { - font-size: 32px; - } - - @media print { - @page { - size: Letter; - width: 8.5in; - height: 11in; - } - }''' + reports.append(page_content) + return reports diff --git a/framework/python/src/common/util.py b/framework/python/src/common/util.py index 7c31631fb..ba1b23e81 100644 --- a/framework/python/src/common/util.py +++ b/framework/python/src/common/util.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Provides basic utilities for the network orchestrator.""" +"""Provides basic utilities for Testrun.""" import getpass import os import subprocess @@ -52,12 +52,15 @@ def run_command(cmd, output=True, timeout=None): def interface_exists(interface): + """Checks whether an interface is available""" return interface in netifaces.interfaces() def prettify(mac_string): + """Formats a MAC address with colons""" return ':'.join([f'{ord(b):02x}' for b in mac_string]) def get_host_user(): + """Returns the username of the host user""" user = get_os_user() # If primary method failed, try secondary @@ -67,6 +70,7 @@ def get_host_user(): return user def get_os_user(): + """Attempts to get the username using os library""" user = None try: user = os.getlogin() @@ -79,6 +83,7 @@ def get_os_user(): return user def get_user(): + """Attempts to get the host user using the getpass library""" user = None try: user = getpass.getuser() @@ -97,9 +102,11 @@ def get_user(): return user def set_file_owner(path, owner): + """Change the owner of a file""" run_command(f'chown -R {owner} {path}') def get_module_display_name(search): + """Returns the display name of a test module""" modules = { 'ntp': 'NTP', 'dns': 'DNS', diff --git a/framework/python/src/core/docker/docker_module.py b/framework/python/src/core/docker/docker_module.py new file mode 100644 index 000000000..21dabdc16 --- /dev/null +++ b/framework/python/src/core/docker/docker_module.py @@ -0,0 +1,163 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Represents the base module.""" +import docker +from docker.models.containers import Container +import os +from common import logger +import json + +IMAGE_PREFIX = 'testrun/' +CONTAINER_PREFIX = 'tr-ct' +DEFAULT_NETWORK = 'bridge' + + +class Module: + """Represents the base module.""" + + def __init__(self, + module_config_file, + session, + docker_network=DEFAULT_NETWORK, + extra_hosts=None): + self._session = session + self.extra_hosts = extra_hosts + + # Read the config file into a json object + with open(module_config_file, encoding='UTF-8') as config_file: + module_json = json.load(config_file) + + self.docker_network = docker_network + # General module information + self.name = module_json['config']['meta']['name'] + self.display_name = module_json['config']['meta']['display_name'] + self.description = module_json['config']['meta']['description'] + self.enabled = module_json['config'].get('enabled', True) + self.depends_on = module_json['config']['docker'].get('depends_on', None) + + # Absolute path + # Store the root directory of Testrun based on the expected locatoin + # Testrun/modules///conf -> 5 levels + self.root_path = os.path.abspath( + os.path.join(module_config_file, '../../../../..')) + self.dir = os.path.dirname(os.path.dirname(module_config_file)) + self.dir_name = os.path.basename(self.dir) + + # Docker settings + self.build_file = self.dir_name + '.Dockerfile' + self.image_name = f'{IMAGE_PREFIX}{self.dir_name}' + self.container_name = f'{CONTAINER_PREFIX}-{self.dir_name}' + if 'tests' in module_json['config']: + # Append Test module + self.image_name += '-test' + self.container_name += '-test' + self.enable_container = module_json['config']['docker'].get( + 'enable_container', True) + self.container: Container = None + + self._add_logger(log_name=self.name, module_name=self.name) + self.setup_module(module_json) + + def _add_logger(self, log_name, module_name, log_dir=None): + self.logger = logger.get_logger( + name=f'{log_name}_module', # pylint: disable=E1123 + log_file=f'{module_name}_module', + log_dir=log_dir) + + def build(self): + self.logger.debug('Building module ' + self.dir_name) + client = docker.from_env() + client.images.build( + dockerfile=os.path.join(self.dir, self.build_file), + path=self._path, + forcerm=True, # Cleans up intermediate containers during build + tag=self.image_name) + + def get_container(self): + container = None + try: + client = docker.from_env() + container = client.containers.get(self.container_name) + except docker.errors.NotFound: + self.logger.debug('Container ' + self.container_name + ' not found') + except docker.errors.APIError as error: + self.logger.error('Failed to resolve container') + self.logger.error(error) + return container + + def get_session(self): + return self._session + + def get_status(self): + self.container = self.get_container() + if self.container is not None: + return self.container.status + return None + + def get_network(self): + return self.docker_network + + def get_mounts(self): + return [] + + def get_environment(self, device=None): # pylint: disable=W0613 + return {} + + def setup_module(self, module_json): + pass + + def _setup_runtime(self, device=None): + pass + + def start(self, device=None): + self._setup_runtime(device) + + self.logger.debug('Starting module ' + self.display_name) + network = self.get_network() + self.logger.debug(f"""Network: {network}, image name: {self.image_name}, + container name: {self.container_name}""") + + try: + client = docker.from_env() + self.container = client.containers.run( + self.image_name, + auto_remove=True, + cap_add=['NET_ADMIN'], + name=self.container_name, + hostname=self.container_name, + network_mode=network, + privileged=True, + detach=True, + mounts=self.get_mounts(), + environment=self.get_environment(device), + extra_hosts=self.extra_hosts if self.extra_hosts is not None else {}) + except docker.errors.ContainerError as error: + self.logger.error('Container run error') + self.logger.error(error) + + def stop(self, kill=False): + self.logger.debug('Stopping module ' + self.container_name) + try: + container = self.get_container() + if container is not None: + if kill: + self.logger.debug('Killing container: ' + self.container_name) + container.kill() + else: + self.logger.debug('Stopping container: ' + self.container_name) + container.stop() + self.logger.debug('Container stopped: ' + self.container_name) + except Exception as error: # pylint: disable=W0703 + self.logger.error('Container stop error') + self.logger.error(error) diff --git a/framework/python/src/core/docker/network_docker_module.py b/framework/python/src/core/docker/network_docker_module.py new file mode 100644 index 000000000..6c892092a --- /dev/null +++ b/framework/python/src/core/docker/network_docker_module.py @@ -0,0 +1,98 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Represents a test module.""" +from core.docker.docker_module import Module +import os +from docker.types import Mount + +RUNTIME_DIR = 'runtime' +RUNTIME_TEST_DIR = os.path.join(RUNTIME_DIR, 'test') +DEFAULT_TIMEOUT = 60 # time in seconds +DEFAULT_DOCKER_NETWORK = 'none' + + +class NetworkModule(Module): + """Represents a test module.""" + + def __init__(self, module_config_file, session): + super().__init__(module_config_file=module_config_file, + docker_network=DEFAULT_DOCKER_NETWORK, + session=session) + + def setup_module(self, module_json): + self.template = module_json['config']['docker'].get('template', False) + self.net_config = NetworkModuleNetConfig() + if self.enable_container: + self.net_config.enable_wan = module_json['config']['network'].get( + 'enable_wan', False) + self.net_config.host = module_json['config']['network'].get('host', False) + # Override default network if host is requested + if self.net_config.host: + self.docker_network = 'host' + + if not self.net_config.host: + self.net_config.ip_index = module_json['config']['network'].get( + 'ip_index') + + self.net_config.ipv4_address = self.get_session().get_ipv4_subnet()[ + self.net_config.ip_index] + self.net_config.ipv4_network = self.get_session().get_ipv4_subnet() + + self.net_config.ipv6_address = self.get_session().get_ipv6_subnet()[ + self.net_config.ip_index] + + self.net_config.ipv6_network = self.get_session().get_ipv6_subnet() + + self._mounts = [] + if 'mounts' in module_json['config']['docker']: + for mount_point in module_json['config']['docker']['mounts']: + self._mounts.append( + Mount(target=mount_point['target'], + source=os.path.join(os.getcwd(), mount_point['source']), + type='bind')) + + def _setup_runtime(self, device): + pass + + def get_environment(self, device=None): # pylint: disable=W0613 + environment = { + 'TZ': self.get_session().get_timezone(), + 'HOST_USER': self.get_session().get_host_user() + } + return environment + + def get_mounts(self): + return self._mounts + + +class NetworkModuleNetConfig: + """Define all the properties of the network config for a network module""" + + def __init__(self): + + self.enable_wan = False + + self.ip_index = 0 + self.ipv4_address = None + self.ipv4_network = None + self.ipv6_address = None + self.ipv6_network = None + + self.host = False + + def get_ipv4_addr_with_prefix(self): + return format(self.ipv4_address) + '/' + str(self.ipv4_network.prefixlen) + + def get_ipv6_addr_with_prefix(self): + return format(self.ipv6_address) + '/' + str(self.ipv6_network.prefixlen) diff --git a/framework/python/src/core/docker/test_docker_module.py b/framework/python/src/core/docker/test_docker_module.py new file mode 100644 index 000000000..4bbf72594 --- /dev/null +++ b/framework/python/src/core/docker/test_docker_module.py @@ -0,0 +1,135 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Represents a test module.""" +from core.docker.docker_module import Module +from test_orc.test_case import TestCase +import os +import json +from common import util +from docker.types import Mount + +RUNTIME_DIR = 'runtime' +RUNTIME_TEST_DIR = os.path.join(RUNTIME_DIR, 'test') +DEFAULT_TIMEOUT = 60 # time in seconds + + +class TestModule(Module): + """Represents a test module.""" + + def __init__(self, module_config_file, session, extra_hosts): + super().__init__(module_config_file=module_config_file, + session=session, + extra_hosts=extra_hosts) + + # Set IP Index for all test modules + self.ip_index = 9 + + def setup_module(self, module_json): + # Set the defaults + self.network = True + self.total_tests = 0 + self.time = DEFAULT_TIMEOUT + self.tests: list = [] + + if 'timeout' in module_json['config']['docker']: + self.timeout = module_json['config']['docker']['timeout'] + + # Determine if this module needs network access + if 'network' in module_json['config']: + self.network = module_json['config']['network'] + + # Load test cases + if 'tests' in module_json['config']: + self.total_tests = len(module_json['config']['tests']) + for test_case_json in module_json['config']['tests']: + try: + test_case = TestCase( + name=test_case_json['name'], + description=test_case_json['test_description'], + expected_behavior=test_case_json['expected_behavior']) + + # Check if steps to resolve have been specified + if 'recommendations' in test_case_json: + test_case.recommendations = test_case_json['recommendations'] + + self.tests.append(test_case) + except Exception as error: # pylint: disable=W0718 + self.logger.error('Failed to load test case. See error for details') + self.logger.error(error) + + def _setup_runtime(self, device): + self.device_test_dir = os.path.join(self.root_path, RUNTIME_TEST_DIR, + device.mac_addr.replace(':', '')) + + self.container_runtime_dir = os.path.join(self.device_test_dir, self.name) + os.makedirs(self.container_runtime_dir, exist_ok=True) + + self.container_log_file = os.path.join(self.container_runtime_dir, + 'module.log') + + self.config_file = os.path.join(self.root_path, 'local/system.json') + self.root_certs_dir = os.path.join(self.root_path, 'local/root_certs') + + self.network_runtime_dir = os.path.join(self.root_path, 'runtime/network') + + self.device_startup_capture = os.path.join(self.device_test_dir, + 'startup.pcap') + host_user = self.get_session().get_host_user() + util.run_command(f'chown -R {host_user} {self.device_startup_capture}') + + self.device_monitor_capture = os.path.join(self.device_test_dir, + 'monitor.pcap') + util.run_command(f'chown -R {host_user} {self.device_monitor_capture}') + + def get_environment(self, device): + environment = { + 'TZ': self.get_session().get_timezone(), + 'HOST_USER': self.get_session().get_host_user(), + 'DEVICE_MAC': device.mac_addr, + 'IPV4_ADDR': device.ip_addr, + 'DEVICE_TEST_MODULES': json.dumps(device.test_modules), + 'IPV4_SUBNET': self.get_session().get_ipv4_subnet(), + 'IPV6_SUBNET': self.get_session().get_ipv6_subnet(), + 'DEV_IFACE': self.get_session().get_device_interface(), + 'DEV_IFACE_MAC': self.get_session().get_device_interface_mac_addr() + } + return environment + + def get_mounts(self): + mounts = [ + Mount(target='/testrun/system.json', + source=self.config_file, + type='bind', + read_only=True), + Mount(target='/testrun/root_certs', + source=self.root_certs_dir, + type='bind', + read_only=True), + Mount(target='/runtime/output', + source=self.container_runtime_dir, + type='bind'), + Mount(target='/runtime/network', + source=self.network_runtime_dir, + type='bind', + read_only=True), + Mount(target='/runtime/device/startup.pcap', + source=self.device_startup_capture, + type='bind', + read_only=True), + Mount(target='/runtime/device/monitor.pcap', + source=self.device_monitor_capture, + type='bind', + read_only=True) + ] + return mounts diff --git a/framework/python/src/common/session.py b/framework/python/src/core/session.py similarity index 76% rename from framework/python/src/common/session.py rename to framework/python/src/core/session.py index 940fbe8f0..f65da0bb5 100644 --- a/framework/python/src/common/session.py +++ b/framework/python/src/core/session.py @@ -20,6 +20,7 @@ from fastapi.encoders import jsonable_encoder from common import util, logger, mqtt from common.risk_profile import RiskProfile +from common.statuses import TestrunStatus, TestResult from net_orc.ip_control import IPControl # Certificate dependencies @@ -36,10 +37,13 @@ API_URL_KEY = 'api_url' API_PORT_KEY = 'api_port' MAX_DEVICE_REPORTS_KEY = 'max_device_reports' +ORG_NAME_KEY = 'org_name' CERTS_PATH = 'local/root_certs' CONFIG_FILE_PATH = 'local/system.json' STATUS_TOPIC = 'status' +MAKE_CONTROL_DIR = 'make/DEBIAN/control' + PROFILE_FORMAT_PATH = 'resources/risk_assessment.json' PROFILES_DIR = 'local/risk_profiles' @@ -52,7 +56,7 @@ def wrapper(self, *args, **kwargs): result = method(self, *args, **kwargs) - if self.get_status() != 'Idle': + if self.get_status() != TestrunStatus.IDLE: self.get_mqtt_client().send_message( STATUS_TOPIC, jsonable_encoder(self.to_json()) @@ -72,7 +76,6 @@ def apply_session_tracker(cls): setattr(cls, attr, session_tracker(getattr(cls, attr))) return cls - @apply_session_tracker class TestrunSession(): """Represents the current session of Testrun.""" @@ -80,7 +83,8 @@ class TestrunSession(): def __init__(self, root_dir): self._root_dir = root_dir - self._status = 'Idle' + self._status = TestrunStatus.IDLE + self._description = None # Target test device self._device = None @@ -130,6 +134,13 @@ def __init__(self, root_dir): self._load_config() self._load_profiles() + # Network information + self._ipv4_subnet = None + self._ipv6_subnet = None + + # Store host user for permissions use + self._host_user = util.get_host_user() + self._certs = [] self.load_certs() @@ -144,7 +155,7 @@ def __init__(self, root_dir): def start(self): self.reset() - self._status = 'Waiting for Device' + self._status = TestrunStatus.WAITING_FOR_DEVICE self._started = datetime.datetime.now() def get_started(self): @@ -154,14 +165,14 @@ def get_finished(self): return self._finished def stop(self): - self.set_status('Stopping') + self.set_status(TestrunStatus.STOPPING) self.finish() def finish(self): # Set any in progress test results to Error for test_result in self._results: - if test_result.result == 'In Progress': - test_result.result = 'Error' + if test_result.result == TestResult.IN_PROGRESS: + test_result.result = TestResult.ERROR self._finished = datetime.datetime.now() @@ -176,7 +187,9 @@ def _get_default_config(self): 'monitor_period': 30, 'max_device_reports': 0, 'api_url': 'http://localhost', - 'api_port': 8000 + 'api_port': 8000, + 'org_name': '', + 'single_intf': False, } def get_config(self): @@ -196,7 +209,7 @@ def _load_config(self): # Network interfaces if (NETWORK_KEY in config_file_json and DEVICE_INTF_KEY in config_file_json.get(NETWORK_KEY) - and INTERNET_INTF_KEY in config_file_json.get(NETWORK_KEY)): + and INTERNET_INTF_KEY in config_file_json.get(NETWORK_KEY)): self._config[NETWORK_KEY][DEVICE_INTF_KEY] = config_file_json.get( NETWORK_KEY, {}).get(DEVICE_INTF_KEY) self._config[NETWORK_KEY][INTERNET_INTF_KEY] = config_file_json.get( @@ -223,13 +236,16 @@ def _load_config(self): self._config[MAX_DEVICE_REPORTS_KEY] = config_file_json.get( MAX_DEVICE_REPORTS_KEY) - LOGGER.debug(self._config) + if ORG_NAME_KEY in config_file_json: + self._config[ORG_NAME_KEY] = config_file_json.get( + ORG_NAME_KEY + ) def _load_version(self): version_cmd = util.run_command( 'dpkg-query --showformat=\'${Version}\' --show testrun') # index 1 of response is the stderr byte stream so if - # it has any data in it, there was an error and we + # it has any data in it, there was an error and wen # did not resolve the version and we'll use the fallback if len(version_cmd[1]) == 0: version = version_cmd[0] @@ -237,14 +253,37 @@ def _load_version(self): else: LOGGER.debug('Failed getting the version from dpkg-query') # Try getting the version from the make control file + + # Check if MAKE_CONTROL_DIR exists + if not os.path.exists(MAKE_CONTROL_DIR): + LOGGER.error('make/DEBIAN/control file path not found') + self._version = 'Unknown' + return + try: - version = util.run_command( - '$(grep -R "Version: " $MAKE_CONTROL_DIR | awk "{print $2}"') - except Exception as e: + # Run the grep command to find the version line + grep_cmd = util.run_command(f'grep -R "Version: " {MAKE_CONTROL_DIR}') + + if grep_cmd[0] and len(grep_cmd[1]) == 0: + # Extract the version number from grep + version = grep_cmd[0].split()[1] + self._version = version + LOGGER.debug(f'Testrun version is: {self._version}') + + else: + # Error handling if grep can't find the version line + self._version = 'Unknown' + LOGGER.debug(f'Testrun version is {self._version}') + raise Exception('Version line not found in make control file') + + except Exception as e: # pylint: disable=W0703 LOGGER.debug('Failed getting the version from make control file') LOGGER.error(e) self._version = 'Unknown' + def get_host_user(self): + return self._host_user + def get_version(self): return self._version @@ -260,11 +299,17 @@ def get_runtime_params(self): return self._runtime_params def add_runtime_param(self, param): + if param == 'single_intf': + self._config['single_intf'] = True self._runtime_params.append(param) def get_device_interface(self): return self._config.get(NETWORK_KEY, {}).get(DEVICE_INTF_KEY) + def get_device_interface_mac_addr(self): + iface = self.get_device_interface() + return IPControl.get_iface_mac_address(iface=iface) + def get_internet_interface(self): return self._config.get(NETWORK_KEY, {}).get(INTERNET_INTF_KEY) @@ -288,7 +333,7 @@ def set_config(self, config_json): self._save_config() # Update log level - LOGGER.debug(f'Setting log level to {config_json["log_level"]}') + LOGGER.debug(f'Setting log level to {config_json["log_level"]}') # pylint: disable=W1405 logger.set_log_level(config_json['log_level']) def set_target_device(self, device): @@ -326,12 +371,21 @@ def get_device(self, mac_addr): def remove_device(self, device): self._device_repository.remove(device) + def get_ipv4_subnet(self): + return self._ipv4_subnet + + def get_ipv6_subnet(self): + return self._ipv6_subnet + def get_status(self): return self._status def set_status(self, status): self._status = status + def set_description(self, desc: str): + self._description = desc + def get_test_results(self): return self._results @@ -357,19 +411,48 @@ def add_test_result(self, result): # result type is TestCase object if test_result.name == result.name: - # Just update the result and description - test_result.result = result.result - test_result.description = result.description - test_result.recommendations = result.recommendations + # Just update the result, description and recommendations + if len(result.description) != 0: + test_result.description = result.description + + # Add recommendations if provided + if result.recommendations is not None: + test_result.recommendations = result.recommendations + + if len(result.recommendations) == 0: + test_result.recommendations = None + + if result.result is not None: + + # Any informational test should always report informational + if test_result.required_result == 'Informational': + + # Set test result to informational + if result.result in [ + TestResult.NON_COMPLIANT, + TestResult.COMPLIANT, + TestResult.INFORMATIONAL + ]: + test_result.result = TestResult.INFORMATIONAL + else: + test_result.result = result.result + + # Copy any test recommendations to optional + test_result.optional_recommendations = result.recommendations + + # Remove recommendations from informational tests + test_result.recommendations = None + else: + test_result.result = result.result + updated = True if not updated: - result.result = 'In Progress' self._results.append(result) def set_test_result_error(self, result): """Set test result error""" - result.result = 'Error' + result.result = TestResult.ERROR result.recommendations = None self._results.append(result) @@ -398,15 +481,18 @@ def get_report_url(self): def set_report_url(self, url): self._report_url = url + def set_subnets(self, ipv4_subnet, ipv6_subnet): + self._ipv4_subnet = ipv4_subnet + self._ipv6_subnet = ipv6_subnet + def _load_profiles(self): # Load format of questionnaire LOGGER.debug('Loading risk assessment format') try: - with open(os.path.join( - self._root_dir, PROFILE_FORMAT_PATH - ), encoding='utf-8') as profile_format_file: + with open(os.path.join(self._root_dir, PROFILE_FORMAT_PATH), + encoding='utf-8') as profile_format_file: format_json = json.load(profile_format_file) # Save original profile format for internal validation self._profile_format = format_json @@ -415,6 +501,10 @@ def _load_profiles(self): 'An error occurred whilst loading the risk assessment format') LOGGER.debug(e) + # If the format JSON fails to load, skip loading profiles + LOGGER.error('Profiles will not be loaded') + return + profile_format_array = [] # Remove internal properties @@ -439,7 +529,7 @@ def _load_profiles(self): try: for risk_profile_file in os.listdir( - os.path.join(self._root_dir, PROFILES_DIR)): + os.path.join(self._root_dir, PROFILES_DIR)): LOGGER.debug(f'Discovered profile {risk_profile_file}') @@ -448,7 +538,7 @@ def _load_profiles(self): encoding='utf-8') as f: # Parse risk profile json - json_data = json.load(f) + json_data: dict = json.load(f) # Validate profile JSON if not self.validate_profile_json(json_data): @@ -456,18 +546,22 @@ def _load_profiles(self): continue # Instantiate a new risk profile - risk_profile = RiskProfile() + risk_profile: RiskProfile = RiskProfile() + + # Assign the profile questions + questions: list[dict] = json_data.get('questions') + + # Pass only the valid questions to the risk profile + json_data['questions'] = self._remove_invalid_questions(questions) # Pass JSON to populate risk profile - risk_profile.load( - profile_json=json_data, - profile_format=self._profile_format - ) + risk_profile.load(profile_json=json_data, + profile_format=self._profile_format) # Add risk profile to session self._profiles.append(risk_profile) - except Exception as e: + except Exception as e: # pylint: disable=W0703 LOGGER.error('An error occurred whilst loading risk profiles') LOGGER.debug(e) @@ -506,14 +600,19 @@ def update_profile(self, profile_json): profile_json['version'] = self.get_version() profile_json['created'] = datetime.datetime.now().strftime('%Y-%m-%d') + # Assign the profile questions + questions: list[dict] = profile_json.get('questions') + + # Pass only the valid questions to the risk profile + profile_json['questions'] = self._remove_invalid_questions(questions) + # Check if profile already exists risk_profile = self.get_profile(profile_name) if risk_profile is None: # Create a new risk profile - risk_profile = RiskProfile( - profile_json=profile_json, - profile_format=self._profile_format) + risk_profile = RiskProfile(profile_json=profile_json, + profile_format=self._profile_format) self._profiles.append(risk_profile) else: @@ -536,6 +635,28 @@ def update_profile(self, profile_json): return risk_profile + def _remove_invalid_questions(self, questions: list[dict]) -> list[dict]: + """Remove unrecognised questions from the profile""" + + # Store valid questions + valid_questions = [] + + # Remove any additional (outdated questions from the profile) + for question in questions: + + # Check if question exists in the profile format + if self.get_profile_format_question( + question=question['question']) is not None: + + # Add the question to the valid_questions + valid_questions.append(question) + + else: + LOGGER.debug(f'Removed unrecognised question: {question["question"]}') + + # Return the list of valid questions + return valid_questions + def validate_profile_json(self, profile_json): """Validate properties in profile update requests""" @@ -572,13 +693,14 @@ def validate_profile_json(self, profile_json): LOGGER.error('A question is missing from "question" field') return False - # Check if question is a recognized question + # Check if question is a recognised question format_q = self.get_profile_format_question( question.get('question')) if format_q is None: - LOGGER.error(f'Unrecognized question: {question.get("question")}') - return False + LOGGER.error(f'Unrecognised question: {question.get("question")}') + # Just ignore additional questions + continue # Error handling if 'answer' is missing if 'answer' not in question and valid: @@ -649,13 +771,14 @@ def delete_profile(self, profile): return True - except Exception as e: + except Exception as e: # pylint: disable=W0703 LOGGER.error('An error occurred whilst deleting a profile') LOGGER.debug(e) return False def reset(self): - self.set_status('Idle') + self.set_status(TestrunStatus.IDLE) + self.set_description(None) self.set_target_device(None) self._report_url = None self._total_tests = 0 @@ -688,6 +811,9 @@ def to_json(self): if self._report_url is not None: session_json['report'] = self.get_report_url() + if self._description is not None: + session_json['description'] = self._description + return session_json def get_timezone(self): @@ -789,7 +915,7 @@ def load_certs(self): self._certs.append(cert_obj) LOGGER.debug(f'Successfully loaded {cert_file}') - except Exception as e: + except Exception as e: # pylint: disable=W0703 LOGGER.error(f'An error occurred whilst loading {cert_file}') LOGGER.debug(e) @@ -809,7 +935,7 @@ def delete_cert(self, filename): self._certs.remove(cert) return True - except Exception as e: + except Exception as e: # pylint: disable=W0703 LOGGER.error('An error occurred whilst deleting the certificate') LOGGER.debug(e) return False diff --git a/framework/python/src/core/tasks.py b/framework/python/src/core/tasks.py new file mode 100644 index 000000000..5da0b40c9 --- /dev/null +++ b/framework/python/src/core/tasks.py @@ -0,0 +1,78 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Periodic background tasks""" + +from contextlib import asynccontextmanager +import datetime +import logging + +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from fastapi import FastAPI + +from common import logger + +# Check adapters period seconds +# Check adapters period seconds +CHECK_NETWORK_ADAPTERS_PERIOD = 5 +CHECK_INTERNET_PERIOD = 2 +INTERNET_CONNECTION_TOPIC = 'events/internet' +NETWORK_ADAPTERS_TOPIC = 'events/adapter' + +LOGGER = logger.get_logger('tasks') + + +class PeriodicTasks: + """Background periodic tasks + """ + def __init__( + self, testrun_obj, + ) -> None: + self._testrun = testrun_obj + self._mqtt_client = self._testrun.get_mqtt_client() + local_tz = datetime.datetime.now().astimezone().tzinfo + self._scheduler = AsyncIOScheduler(timezone=local_tz) + # Prevent scheduler warnings + self._scheduler._logger.setLevel(logging.ERROR) + + self.adapters_checker_job = self._scheduler.add_job( + func=self._testrun.get_net_orc().network_adapters_checker, + kwargs={ + 'mqtt_client': self._mqtt_client, + 'topic': NETWORK_ADAPTERS_TOPIC + }, + trigger='interval', + seconds=CHECK_NETWORK_ADAPTERS_PERIOD, + ) + # add internet connection cheking job only in single-intf mode + if 'single_intf' not in self._testrun.get_session().get_runtime_params(): + self.internet_shecker = self._scheduler.add_job( + func=self._testrun.get_net_orc().internet_conn_checker, + kwargs={ + 'mqtt_client': self._mqtt_client, + 'topic': INTERNET_CONNECTION_TOPIC + }, + trigger='interval', + seconds=CHECK_INTERNET_PERIOD, + ) + + @asynccontextmanager + async def start(self, app: FastAPI): # pylint: disable=unused-argument + """Start background tasks + + Args: + app (FastAPI): app instance + """ + # Job that checks for changes in network adapters + self._scheduler.start() + yield diff --git a/framework/python/src/core/test_runner.py b/framework/python/src/core/test_runner.py index 870e97752..a295f47e1 100644 --- a/framework/python/src/core/test_runner.py +++ b/framework/python/src/core/test_runner.py @@ -39,7 +39,7 @@ def __init__(self, single_intf=False, no_ui=False): self._register_exits() - self.test_run = Testrun(config_file=config_file, + self._testrun = Testrun(config_file=config_file, validate=validate, net_only=net_only, single_intf=single_intf, @@ -62,7 +62,7 @@ def _exit_handler(self, signum, arg): # pylint: disable=unused-argument sys.exit(1) def stop(self): - self.test_run.stop() + self._testrun.stop() def parse_args(): diff --git a/framework/python/src/core/testrun.py b/framework/python/src/core/testrun.py index dccde6a35..1855b71b5 100644 --- a/framework/python/src/core/testrun.py +++ b/framework/python/src/core/testrun.py @@ -12,13 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""The overall control of the Test Run application. - +"""The overall control of the Testrun application. This file provides the integration between all of the Testrun components, such as net_orc, test_orc and test_ui. - -Run using the provided command scripts in the cmd folder. -E.g sudo cmd/start """ import docker import json @@ -29,8 +25,9 @@ import time from common import logger, util, mqtt from common.device import Device -from common.session import TestrunSession from common.testreport import TestReport +from common.statuses import TestrunStatus +from session import TestrunSession from api.api import Api from net_orc.listener import NetworkEvent from net_orc import network_orchestrator as net_orc @@ -38,14 +35,7 @@ from docker.errors import ImageNotFound -# Locate parent directory -current_dir = os.path.dirname(os.path.realpath(__file__)) - -# Locate the test-run root directory, 4 levels, src->python->framework->test-run -root_dir = os.path.dirname(os.path.dirname( - os.path.dirname(os.path.dirname(current_dir)))) - -LOGGER = logger.get_logger('test_run') +LOGGER = logger.get_logger('testrun') DEFAULT_CONFIG_FILE = 'local/system.json' EXAMPLE_CONFIG_FILE = 'local/system.json.example' @@ -58,10 +48,15 @@ DEVICE_MODEL = 'model' DEVICE_MAC_ADDR = 'mac_addr' DEVICE_TEST_MODULES = 'test_modules' +DEVICE_TYPE_KEY = 'type' +DEVICE_TECHNOLOGY_KEY = 'technology' +DEVICE_TEST_PACK_KEY = 'test_pack' +DEVICE_ADDITIONAL_INFO_KEY = 'additional_info' + MAX_DEVICE_REPORTS_KEY = 'max_device_reports' class Testrun: # pylint: disable=too-few-public-methods - """Test Run controller. + """Testrun controller. Creates an instance of the network orchestrator, test orchestrator and user interface. @@ -74,6 +69,15 @@ def __init__(self, single_intf=False, no_ui=False): + # Locate parent directory + current_dir = os.path.dirname(os.path.realpath(__file__)) + + # Locate the test-run root directory, 4 levels, + # src->python->framework->test-run + self._root_dir = os.path.dirname(os.path.dirname( + os.path.dirname(os.path.dirname(current_dir)))) + + # Determine config file if config_file is None: self._config_file = self._get_config_abs(DEFAULT_CONFIG_FILE) else: @@ -89,7 +93,7 @@ def __init__(self, self._register_exits() # Create session - self._session = TestrunSession(root_dir=root_dir) + self._session = TestrunSession(root_dir=self._root_dir) # Register runtime parameters if single_intf: @@ -139,6 +143,9 @@ def __init__(self, while True: time.sleep(1) + def get_root_dir(self): + return self._root_dir + def get_version(self): return self.get_session().get_version() @@ -165,7 +172,7 @@ def _load_devices(self, device_dir): # Check if device config file exists before loading if not os.path.exists(device_config_file_path): LOGGER.error('Device configuration file missing ' + - f'from device {device_folder}') + f'for device {device_folder}') continue # Open device config file @@ -177,6 +184,8 @@ def _load_devices(self, device_dir): device_model = device_config_json.get(DEVICE_MODEL) mac_addr = device_config_json.get(DEVICE_MAC_ADDR) test_modules = device_config_json.get(DEVICE_TEST_MODULES) + + # Load max device reports max_device_reports = None if 'max_device_reports' in device_config_json: max_device_reports = device_config_json.get(MAX_DEVICE_REPORTS_KEY) @@ -191,6 +200,25 @@ def _load_devices(self, device_dir): max_device_reports=max_device_reports, device_folder=device_folder) + # Load in the additional fields + if DEVICE_TYPE_KEY in device_config_json: + device.type = device_config_json.get(DEVICE_TYPE_KEY) + + if DEVICE_TECHNOLOGY_KEY in device_config_json: + device.technology = device_config_json.get(DEVICE_TECHNOLOGY_KEY) + + if DEVICE_TEST_PACK_KEY in device_config_json: + device.test_pack = device_config_json.get(DEVICE_TEST_PACK_KEY) + + if DEVICE_ADDITIONAL_INFO_KEY in device_config_json: + device.additional_info = device_config_json.get( + DEVICE_ADDITIONAL_INFO_KEY) + + if None in [device.type, device.technology, device.test_pack]: + LOGGER.warning( + 'Device is outdated and requires further configuration') + device.status = 'Invalid' + self._load_test_reports(device) # Add device to device repository @@ -200,22 +228,19 @@ def _load_devices(self, device_dir): def _load_test_reports(self, device): - LOGGER.debug(f'Loading test reports for device {device.model}') + LOGGER.debug('Loading test reports for device ' + + f'{device.manufacturer} {device.model}') # Remove the existing reports in memory device.clear_reports() # Locate reports folder - reports_folder = os.path.join(root_dir, - LOCAL_DEVICES_DIR, - device.device_folder, 'reports') + reports_folder = self.get_reports_folder(device) # Check if reports folder exists (device may have no reports) if not os.path.exists(reports_folder): return - LOGGER.info(f'Loading reports from {reports_folder}') - for report_folder in os.listdir(reports_folder): # 1.3 file path report_json_file_path = os.path.join( @@ -251,18 +276,18 @@ def _load_test_reports(self, device): test_report.set_mac_addr(device.mac_addr) device.add_report(test_report) + def get_reports_folder(self, device): + """Return the reports folder path for the device""" + return os.path.join(self._root_dir, + LOCAL_DEVICES_DIR, + device.device_folder, 'reports') + def delete_report(self, device: Device, timestamp): LOGGER.debug(f'Deleting test report for device {device.model} ' + f'at {timestamp}') # Locate reports folder - reports_folder = os.path.join(root_dir, - LOCAL_DEVICES_DIR, - device.device_folder, 'reports') - - # Check if reports folder exists (device may have no reports) - if not os.path.exists(reports_folder): - return False + reports_folder = self.get_reports_folder(device) for report_folder in os.listdir(reports_folder): if report_folder == timestamp: @@ -276,7 +301,7 @@ def delete_report(self, device: Device, timestamp): def create_device(self, device: Device): # Define the device folder location - device_folder_path = os.path.join(root_dir, + device_folder_path = os.path.join(self._root_dir, LOCAL_DEVICES_DIR, device.device_folder) @@ -297,20 +322,11 @@ def create_device(self, device: Device): return device.to_config_json() - def save_device(self, device: Device, device_json): + def save_device(self, device: Device): """Edit and save an existing device config.""" - # Update device properties - device.manufacturer = device_json['manufacturer'] - device.model = device_json['model'] - - if 'test_modules' in device_json: - device.test_modules = device_json['test_modules'] - else: - device.test_modules = {} - # Obtain the config file path - config_file_path = os.path.join(root_dir, + config_file_path = os.path.join(self._root_dir, LOCAL_DEVICES_DIR, device.device_folder, DEVICE_CONFIG) @@ -326,7 +342,7 @@ def save_device(self, device: Device, device_json): def delete_device(self, device: Device): # Obtain the config file path - device_folder = os.path.join(root_dir, + device_folder = os.path.join(self._root_dir, LOCAL_DEVICES_DIR, device.device_folder) @@ -364,15 +380,21 @@ def start(self): def stop(self): + # First, change the status to stopping + self.get_session().stop() + # Prevent discovering new devices whilst stopping if self.get_net_orc().get_listener() is not None: self.get_net_orc().get_listener().stop_listener() - self.get_session().stop() - self._stop_tests() + + self.get_session().set_status(TestrunStatus.CANCELLED) + + # Disconnect before WS server stops to prevent error + self._mqtt_client.disconnect() + self._stop_network(kill=True) - self.get_session().set_status('Cancelled') def _register_exits(self): signal.signal(signal.SIGINT, self._exit_handler) @@ -396,14 +418,11 @@ def _exit_handler(self, signum, arg): # pylint: disable=unused-argument def _get_config_abs(self, config_file=None): if config_file is None: # If not defined, use relative pathing to local file - config_file = os.path.join(root_dir, self._config_file) + config_file = os.path.join(self._root_dir, self._config_file) # Expand the config file to absolute pathing return os.path.abspath(config_file) - def get_root_dir(self): - return root_dir - def get_config_file(self): return self._get_config_abs() @@ -457,16 +476,17 @@ def _device_discovered(self, mac_addr): def _device_stable(self, mac_addr): # Do not continue testing if Testrun has cancelled during monitor phase - if self.get_session().get_status() == 'Cancelled': + if self.get_session().get_status() == TestrunStatus.CANCELLED: self._stop_network() return LOGGER.info(f'Device with mac address {mac_addr} is ready for testing.') - self._set_status('In Progress') + self._set_status(TestrunStatus.IN_PROGRESS) result = self._test_orc.run_test_modules() if result is not None: self._set_status(result) + self._stop_network() def get_session(self): @@ -513,7 +533,6 @@ def _stop_ui(self): except docker.errors.NotFound: pass - def start_ws(self): self._stop_ws() diff --git a/framework/python/src/net_orc/ip_control.py b/framework/python/src/net_orc/ip_control.py index 04686f0cd..aa07283af 100644 --- a/framework/python/src/net_orc/ip_control.py +++ b/framework/python/src/net_orc/ip_control.py @@ -17,6 +17,7 @@ from common import logger from common import util import re +import socket LOGGER = logger.get_logger('ip_ctrl') @@ -88,6 +89,16 @@ def get_iface_connection_stats(self, iface): else: return None + @staticmethod + def get_iface_mac_address(iface): + net_if_addrs = psutil.net_if_addrs() + if iface in net_if_addrs: + for addr_info in net_if_addrs[iface]: + # AF_LINK corresponds to the MAC address + if addr_info.family == psutil.AF_LINK: + return addr_info.address + return None + def get_iface_port_stats(self, iface): """Extract information about packets connection""" response = util.run_command(f'ethtool -S {iface}') @@ -96,6 +107,14 @@ def get_iface_port_stats(self, iface): else: return None + def get_ip_address(self, iface): + addrs = psutil.net_if_addrs() + if iface in addrs: + for addr in addrs[iface]: + if addr.family == socket.AF_INET: + return addr.address + return None + def get_namespaces(self): result = util.run_command('ip netns list') # Strip ID's from the namespace results diff --git a/framework/python/src/net_orc/network_orchestrator.py b/framework/python/src/net_orc/network_orchestrator.py index a94bca89b..b8e7befd2 100644 --- a/framework/python/src/net_orc/network_orchestrator.py +++ b/framework/python/src/net_orc/network_orchestrator.py @@ -17,19 +17,20 @@ import json import os from scapy.all import sniff, wrpcap, BOOTP, AsyncSniffer +from scapy.error import Scapy_Exception import shutil import subprocess import sys -import docker import time import traceback -from docker.types import Mount from common import logger, util, mqtt +from common.statuses import TestrunStatus from net_orc.listener import Listener from net_orc.network_event import NetworkEvent from net_orc.network_validator import NetworkValidator from net_orc.ovs_control import OVSControl from net_orc.ip_control import IPControl +from core.docker.network_docker_module import NetworkModule LOGGER = logger.get_logger('net_orc') RUNTIME_DIR = 'runtime' @@ -67,6 +68,10 @@ def __init__(self, session): self._ovs = OVSControl(self._session) self._ip_ctrl = IPControl() + # Load subnet information into the session + self._session.set_subnets(self.network_config.ipv4_network, + self.network_config.ipv6_network) + def start(self): """Start the network orchestrator.""" @@ -138,6 +143,9 @@ def start_network(self): # Get network ready (via Network orchestrator) LOGGER.debug('Network is ready') + def get_ip_address(self, iface): + return self._ip_ctrl.get_ip_address(iface) + def get_listener(self): return self._listener @@ -191,7 +199,7 @@ def _device_discovered(self, mac_addr): test_dir = os.path.join(RUNTIME_DIR, TEST_DIR) device_tests = os.listdir(test_dir) for device_test in device_tests: - device_test_path = os.path.join(RUNTIME_DIR,TEST_DIR,device_test) + device_test_path = os.path.join(RUNTIME_DIR, TEST_DIR, device_test) if os.path.isdir(device_test_path): shutil.rmtree(device_test_path, ignore_errors=True) @@ -216,7 +224,7 @@ def _device_discovered(self, mac_addr): if device.ip_addr is None: LOGGER.info( f'Timed out whilst waiting for {mac_addr} to obtain an IP address') - self._session.set_status('Cancelled') + self._session.set_status(TestrunStatus.CANCELLED) return LOGGER.info( f'Device with mac addr {device.mac_addr} has obtained IP address ' @@ -276,7 +284,7 @@ def _dhcp_lease_ack(self, packet): def _start_device_monitor(self, device): """Start a timer until the steady state has been reached and callback the steady state method for this device.""" - self.get_session().set_status('Monitoring') + self.get_session().set_status(TestrunStatus.MONITORING) self._monitor_packets = [] LOGGER.info(f'Monitoring device with mac addr {device.mac_addr} ' f'for {str(self._session.get_monitor_period())} seconds') @@ -293,15 +301,22 @@ def _start_device_monitor(self, device): time.sleep(1) # Check Testrun hasn't been cancelled - if self._session.get_status() == 'Cancelled': + if self._session.get_status() in ( + TestrunStatus.STOPPING, + TestrunStatus.CANCELLED + ): sniffer.stop() return if not self._ip_ctrl.check_interface_status( self._session.get_device_interface()): - sniffer.stop() - self._session.set_status('Cancelled') - LOGGER.error('Device interface disconnected, cancelling Testrun') + try: + sniffer.stop() + except Scapy_Exception: + LOGGER.error('Device adapter disconnected whilst monitoring.') + finally: + self._session.set_status(TestrunStatus.CANCELLED) + LOGGER.error('Device interface disconnected, cancelling Testrun') LOGGER.debug('Writing packets to monitor.pcap') wrpcap(os.path.join(device_runtime_dir, 'monitor.pcap'), @@ -333,26 +348,6 @@ def _ping(self, net_module): success = util.run_command(cmd, output=False) return success - def _create_private_net(self): - client = docker.from_env() - try: - network = client.networks.get(PRIVATE_DOCKER_NET) - network.remove() - except docker.errors.NotFound: - pass - - # TODO: These should be made into variables - ipam_pool = docker.types.IPAMPool(subnet='100.100.0.0/16', - iprange='100.100.100.0/24') - - ipam_config = docker.types.IPAMConfig(pool_configs=[ipam_pool]) - - client.networks.create(PRIVATE_DOCKER_NET, - ipam=ipam_config, - internal=True, - check_duplicate=True, - driver='macvlan') - def _ci_pre_network_create(self): """ Stores network properties to restore network after network creation and flushes internet interface @@ -439,79 +434,33 @@ def load_network_modules(self): for module_dir in os.listdir(net_modules_dir): - if self._get_network_module(module_dir) is None: + if (self._get_network_module(module_dir) is None and + module_dir != 'template'): loaded_module = self._load_network_module(module_dir) loaded_modules += loaded_module.dir_name + ' ' LOGGER.info(loaded_modules) def _load_network_module(self, module_dir): + """Import module configuration from module_config.json.""" - net_modules_dir = os.path.join(self._path, NETWORK_MODULES_DIR) + # Make sure we only load each module once since some modules will + # depend on the same module + if not any(m.dir_name == module_dir for m in self._net_modules): + + LOGGER.debug(f'Loading network module {module_dir}') - net_module = NetworkModule() - - # Load module information - with open(os.path.join(self._path, net_modules_dir, module_dir, - NETWORK_MODULE_METADATA), - 'r', - encoding='UTF-8') as module_file_open: - net_module_json = json.load(module_file_open) - - net_module.name = net_module_json['config']['meta']['name'] - net_module.display_name = net_module_json['config']['meta']['display_name'] - net_module.description = net_module_json['config']['meta']['description'] - net_module.dir = os.path.join(self._path, net_modules_dir, module_dir) - net_module.dir_name = module_dir - net_module.build_file = module_dir + '.Dockerfile' - net_module.container_name = 'tr-ct-' + net_module.dir_name - net_module.image_name = 'test-run/' + net_module.dir_name - - # Attach folder mounts to network module - if 'docker' in net_module_json['config']: - - if 'mounts' in net_module_json['config']['docker']: - for mount_point in net_module_json['config']['docker']['mounts']: - net_module.mounts.append( - Mount(target=mount_point['target'], - source=os.path.join(os.getcwd(), mount_point['source']), - type='bind')) - - if 'depends_on' in net_module_json['config']['docker']: - depends_on_module = net_module_json['config']['docker']['depends_on'] - if self._get_network_module(depends_on_module) is None: - self._load_network_module(depends_on_module) - - # Determine if this is a container or just an image/template - if 'enable_container' in net_module_json['config']['docker']: - net_module.enable_container = net_module_json['config']['docker'][ - 'enable_container'] - - # Determine if this is a template - if 'template' in net_module_json['config']['docker']: - net_module.template = net_module_json['config']['docker']['template'] - - # Load network service networking configuration - if net_module.enable_container: - - net_module.net_config.enable_wan = net_module_json['config']['network'][ - 'enable_wan'] - net_module.net_config.ip_index = net_module_json['config']['network'][ - 'ip_index'] - - net_module.net_config.host = False if not 'host' in net_module_json[ - 'config']['network'] else net_module_json['config']['network']['host'] - - net_module.net_config.ipv4_address = self.network_config.ipv4_network[ - net_module.net_config.ip_index] - net_module.net_config.ipv4_network = self.network_config.ipv4_network - - net_module.net_config.ipv6_address = self.network_config.ipv6_network[ - net_module.net_config.ip_index] - net_module.net_config.ipv6_network = self.network_config.ipv6_network - - self._net_modules.append(net_module) - return net_module + modules_dir = os.path.join(self._path, NETWORK_MODULES_DIR) + + module_conf_file = os.path.join(self._path, modules_dir, module_dir, + NETWORK_MODULE_METADATA) + + module = NetworkModule(module_conf_file, self._session) + if module.depends_on is not None: + self._load_network_module(module.depends_on) + self._net_modules.append(module) + + return module def build_network_modules(self): LOGGER.info('Building network modules...') @@ -521,12 +470,7 @@ def build_network_modules(self): def _build_module(self, net_module): LOGGER.debug('Building network module ' + net_module.dir_name) - client = docker.from_env() - client.images.build(dockerfile=os.path.join(net_module.dir, - net_module.build_file), - path=self._path, - forcerm=True, - tag='test-run/' + net_module.dir_name) + net_module.build() def _get_network_module(self, name): for net_module in self._net_modules: @@ -538,61 +482,17 @@ def _get_network_module(self, name): def _start_network_service(self, net_module): LOGGER.debug('Starting network service ' + net_module.display_name) - network = 'host' if net_module.net_config.host else PRIVATE_DOCKER_NET + network = 'host' if net_module.net_config.host else 'bridge' LOGGER.debug(f"""Network: {network}, image name: {net_module.image_name}, container name: {net_module.container_name}""") - try: - client = docker.from_env() - net_module.container = client.containers.run( - net_module.image_name, - auto_remove=True, - cap_add=['NET_ADMIN'], - name=net_module.container_name, - hostname=net_module.container_name, - network_mode='none', - privileged=True, - detach=True, - mounts=net_module.mounts, - environment={ - 'TZ': self.get_session().get_timezone(), - 'HOST_USER': util.get_host_user() - }) - except docker.errors.ContainerError as error: - LOGGER.error('Container run error') - LOGGER.error(error) - - if network != 'host': + net_module.start() + if net_module.get_network() != 'host': self._attach_service_to_network(net_module) def _stop_service_module(self, net_module, kill=False): LOGGER.debug('Stopping network container ' + net_module.container_name) - try: - container = self._get_service_container(net_module) - if container is not None: - if kill: - LOGGER.debug('Killing container: ' + net_module.container_name) - container.kill() - else: - LOGGER.debug('Stopping container: ' + net_module.container_name) - container.stop() - LOGGER.debug('Container stopped: ' + net_module.container_name) - except Exception as error: # pylint: disable=W0703 - LOGGER.error('Container stop error') - LOGGER.error(error) - - def _get_service_container(self, net_module): - LOGGER.debug('Resolving service container: ' + net_module.container_name) - container = None - try: - client = docker.from_env() - container = client.containers.get(net_module.container_name) - except docker.errors.NotFound: - LOGGER.debug('Container ' + net_module.container_name + ' not found') - except Exception as e: # pylint: disable=W0703 - LOGGER.error('Failed to resolve container') - LOGGER.error(e) - return container + net_module.stop(kill=kill) def stop_networking_services(self, kill=False): LOGGER.info('Stopping network services') @@ -757,13 +657,10 @@ def restore_net(self): if self.get_listener() is not None and self.get_listener().is_running(): self.get_listener().stop_listener() - client = docker.from_env() - # Stop all network containers if still running for net_module in self._net_modules: try: - container = client.containers.get('tr-ct-' + net_module.dir_name) - container.kill() + net_module.stop(kill=True) except Exception: # pylint: disable=W0703 continue @@ -793,7 +690,7 @@ def network_adapters_checker(self, mqtt_client: mqtt.MQTT, topic: str): adapters = self._session.detect_network_adapters_change() if adapters: mqtt_client.send_message(topic, adapters) - except Exception: + except Exception: # pylint: disable=W0703 LOGGER.error(traceback.format_exc()) def is_device_connected(self): @@ -805,22 +702,26 @@ def is_device_connected(self): def internet_conn_checker(self, mqtt_client: mqtt.MQTT, topic: str): """Checks internet connection and sends a status to frontend""" - # Only check if Testrun is running not in single-intf mode - if (self.get_session().get_status() in [ - 'Waiting for Device', - 'Monitoring', - 'In Progress' - ]): - # Default message - message = {'connection': False} + # Default message + message = {'connection': False} + + # Only check if Testrun is running + if self.get_session().get_status() not in [ + TestrunStatus.WAITING_FOR_DEVICE, + TestrunStatus.MONITORING, + TestrunStatus.IN_PROGRESS + ]: + message['connection'] = None + + # Only run if single intf mode not used + elif 'single_intf' not in self._session.get_runtime_params(): iface = self._session.get_internet_interface() # Check that an internet intf has been selected if iface and iface in self._ip_ctrl.get_sys_interfaces(): # Ping google.com from gateway container - internet_connection = self._ip_ctrl.ping_via_gateway( - 'google.com') + internet_connection = self._ip_ctrl.ping_via_gateway('google.com') if internet_connection: message['connection'] = True @@ -828,53 +729,6 @@ def internet_conn_checker(self, mqtt_client: mqtt.MQTT, topic: str): # Broadcast via MQTT client mqtt_client.send_message(topic, message) -class NetworkModule: - """Define all the properties of a Network Module""" - - def __init__(self): - self.name = None - self.display_name = None - self.description = None - - self.container = None - self.container_name = None - self.image_name = None - self.template = False - - # Absolute path - self.dir = None - self.dir_name = None - self.build_file = None - self.mounts = [] - - self.enable_container = True - - self.net_config = NetworkModuleNetConfig() - - -class NetworkModuleNetConfig: - """Define all the properties of the network config - for a network module""" - - def __init__(self): - - self.enable_wan = False - - self.ip_index = 0 - self.ipv4_address = None - self.ipv4_network = None - self.ipv6_address = None - self.ipv6_network = None - - self.host = False - - def get_ipv4_addr_with_prefix(self): - return format(self.ipv4_address) + '/' + str(self.ipv4_network.prefixlen) - - def get_ipv6_addr_with_prefix(self): - return format(self.ipv6_address) + '/' + str(self.ipv6_network.prefixlen) - - class NetworkConfig: """Define all the properties of the network configuration""" diff --git a/framework/python/src/net_orc/network_validator.py b/framework/python/src/net_orc/network_validator.py index df9b96b1d..d760970a3 100644 --- a/framework/python/src/net_orc/network_validator.py +++ b/framework/python/src/net_orc/network_validator.py @@ -106,7 +106,7 @@ def _load_devices(self): device.dir_name = module_dir device.build_file = module_dir + '.Dockerfile' device.container_name = 'tr-ct-' + device.dir_name - device.image_name = 'test-run/' + device.dir_name + device.image_name = 'testrun/' + device.dir_name runtime_source = os.path.join(os.getcwd(), OUTPUT_DIR, device.name) conf_source = os.path.join(os.getcwd(), CONF_DIR) diff --git a/framework/python/src/test_orc/test_case.py b/framework/python/src/test_orc/test_case.py index cf0d6593a..6f4e3434b 100644 --- a/framework/python/src/test_orc/test_case.py +++ b/framework/python/src/test_orc/test_case.py @@ -14,6 +14,7 @@ """Represents an individual test case.""" from dataclasses import dataclass, field +from common.statuses import TestResult @dataclass @@ -24,25 +25,25 @@ class TestCase: # pylint: disable=too-few-public-methods,too-many-instance-attr description: str = "" expected_behavior: str = "" required_result: str = "Recommended" - result: str = "Non-Compliant" + result: str = TestResult.NON_COMPLIANT recommendations: list = field(default_factory=lambda: []) + optional_recommendations: list = field(default_factory=lambda: []) def to_dict(self): + test_dict = { + "name": self.name, + "description": self.description, + "expected_behavior": self.expected_behavior, + "required_result": self.required_result, + "result": self.result + } + if self.recommendations is not None and len(self.recommendations) > 0: - return { - "name": self.name, - "description": self.description, - "expected_behavior": self.expected_behavior, - "required_result": self.required_result, - "result": self.result, - "recommendations": self.recommendations - } - - return { - "name": self.name, - "description": self.description, - "expected_behavior": self.expected_behavior, - "required_result": self.required_result, - "result": self.result - } + test_dict["recommendations"] = self.recommendations + + if (self.optional_recommendations is not None + and len(self.optional_recommendations) > 0): + test_dict["optional_recommendations"] = self.optional_recommendations + + return test_dict diff --git a/framework/python/src/test_orc/test_orchestrator.py b/framework/python/src/test_orc/test_orchestrator.py index a38371d07..4085e91ad 100644 --- a/framework/python/src/test_orc/test_orchestrator.py +++ b/framework/python/src/test_orc/test_orchestrator.py @@ -20,23 +20,32 @@ import shutil import docker from datetime import datetime -from docker.types import Mount from common import logger, util from common.testreport import TestReport -from test_orc.module import TestModule +from common.statuses import TestrunStatus, TestResult +from core.docker.test_docker_module import TestModule from test_orc.test_case import TestCase +from test_orc.test_pack import TestPack import threading +from typing import List LOG_NAME = "test_orc" LOGGER = logger.get_logger("test_orc") + RUNTIME_DIR = "runtime" -RUNTIME_TEST_DIR = os.path.join(RUNTIME_DIR,"test") +RESOURCES_DIR = "resources" + +RUNTIME_TEST_DIR = os.path.join(RUNTIME_DIR, "test") +TEST_PACKS_DIR = os.path.join(RESOURCES_DIR, "test_packs") + TEST_MODULES_DIR = "modules/test" MODULE_CONFIG = "conf/module_config.json" -LOG_REGEX = r"^[A-Z][a-z]{2} [0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} test_" + SAVED_DEVICE_REPORTS = "report/{device_folder}/" LOCAL_DEVICE_REPORTS = "local/devices/{device_folder}/reports" DEVICE_ROOT_CERTS = "local/root_certs" + +LOG_REGEX = r"^[A-Z][a-z]{2} [0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} test_" API_URL = "http://localhost:8000" @@ -44,17 +53,18 @@ class TestOrchestrator: """Manages and controls the test modules.""" def __init__(self, session, net_orc): - self._test_modules = [] + + self._test_modules: List[TestModule] = [] + self._test_packs: List[TestPack] = [] + self._container_logs = [] self._session = session - self._api_url = (self._session.get_api_url() + ":" + - str(self._session.get_api_port())) + + self._api_url = (self.get_session().get_api_url() + ":" + + str(self.get_session().get_api_port())) + self._net_orc = net_orc self._test_in_progress = False - self._path = os.path.dirname( - os.path.dirname( - os.path.dirname( - os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))) self._root_path = os.path.dirname( os.path.dirname( @@ -75,6 +85,7 @@ def start(self): os.makedirs(DEVICE_ROOT_CERTS, exist_ok=True) self._load_test_modules() + self._load_test_packs() def stop(self): """Stop any running tests""" @@ -84,24 +95,55 @@ def run_test_modules(self): """Iterates through each test module and starts the container.""" # Do not start test modules if status is not in progress, e.g. Stopping - if self.get_session().get_status() != "In Progress": + if self.get_session().get_status() != TestrunStatus.IN_PROGRESS: return - device = self._session.get_target_device() + device = self.get_session().get_target_device() + test_pack_name = device.test_pack + test_pack = self.get_test_pack(test_pack_name) + LOGGER.debug("Using test pack " + test_pack.name) + self._test_in_progress = True + LOGGER.info( f"Running test modules on device with mac addr {device.mac_addr}") test_modules = [] + for module in self._test_modules: + # Ignore test modules that are just base images etc if module is None or not module.enable_container: continue + # Ignore test modules that are disabled for this device if not self._is_module_enabled(module, device): continue + # Add module to list of modules to run test_modules.append(module) + + for test in module.tests: + + # Duplicate test obj so we don't alter the source + test_copy = copy.deepcopy(test) + + # Set result to Not Started + test_copy.result = TestResult.NOT_STARTED + + # We don't want steps to resolve for not started tests + if hasattr(test_copy, "recommendations"): + test_copy.recommendations = None + + # Set the required result from the correct test pack + required_result = test_pack.get_required_result(test.name) + + test_copy.required_result = required_result + + # Add test result to the session + self.get_session().add_test_result(test_copy) + + # Increment number of tests that will be run self.get_session().add_total_tests(len(module.tests)) # Store enabled test modules in the TestsOrchectrator object @@ -115,14 +157,16 @@ def run_test_modules(self): LOGGER.info("All tests complete") - self._session.finish() + self.get_session().finish() # Do not carry on (generating a report) if Testrun has been stopped - if self.get_session().get_status() != "In Progress": - return "Cancelled" + if self.get_session().get_status() != TestrunStatus.IN_PROGRESS: + return TestrunStatus.CANCELLED report = TestReport() - report.from_json(self._generate_report()) + + generated_report_json = self._generate_report() + report.from_json(generated_report_json) report.add_module_reports(self.get_session().get_module_reports()) device.add_report(report) @@ -130,6 +174,19 @@ def run_test_modules(self): self._test_in_progress = False self.get_session().set_report_url(report.get_report_url()) + # Set testing description + test_pack: TestPack = self.get_test_pack(device.test_pack) + + # Default message is empty (better than an error message). + # This should never be shown + message: str = "" + if report.get_status() == TestrunStatus.COMPLIANT: + message = test_pack.get_message("compliant_description") + elif report.get_status() == TestrunStatus.NON_COMPLIANT: + message = test_pack.get_message("non_compliant_description") + + self.get_session().set_description(message) + # Move testing output from runtime to local device folder self._timestamp_results(device) @@ -144,7 +201,7 @@ def _write_reports(self, test_report): out_dir = os.path.join( self._root_path, RUNTIME_TEST_DIR, - self._session.get_target_device().mac_addr.replace(":", "")) + self.get_session().get_target_device().mac_addr.replace(":", "")) LOGGER.debug(f"Writing reports to {out_dir}") @@ -165,9 +222,7 @@ def _write_reports(self, test_report): def _generate_report(self): report = {} - report["testrun"] = { - "version": self.get_session().get_version() - } + report["testrun"] = {"version": self.get_session().get_version()} report["mac_addr"] = self.get_session().get_target_device().mac_addr report["device"] = self.get_session().get_target_device().to_dict() @@ -186,16 +241,22 @@ def _generate_report(self): return report def _calculate_result(self): - result = "Compliant" - for test_result in self._session.get_test_results(): + result = TestResult.COMPLIANT + for test_result in self.get_session().get_test_results(): + # Check Required tests if (test_result.required_result.lower() == "required" - and test_result.result.lower() != "compliant"): - result = "Non-Compliant" + and test_result.result not in [ + TestResult.COMPLIANT, + TestResult.ERROR + ]): + result = TestResult.NON_COMPLIANT + # Check Required if Applicable tests elif (test_result.required_result.lower() == "required if applicable" - and test_result.result.lower() == "non-compliant"): - result = "Non-Compliant" + and test_result.result == TestResult.NON_COMPLIANT): + result = TestResult.NON_COMPLIANT + return result def _cleanup_old_test_results(self, device): @@ -203,7 +264,7 @@ def _cleanup_old_test_results(self, device): if device.max_device_reports is not None: max_device_reports = device.max_device_reports else: - max_device_reports = self._session.get_max_device_reports() + max_device_reports = self.get_session().get_max_device_reports() if max_device_reports > 0: completed_results_dir = os.path.join( @@ -278,25 +339,20 @@ def _timestamp_results(self, device): return completed_results_dir - def zip_results(self, - device, - timestamp, - profile): + def zip_results(self, device, timestamp, profile): try: LOGGER.debug("Archiving test results") - src_path = os.path.join(LOCAL_DEVICE_REPORTS.replace( - "{device_folder}", - device.device_folder), - timestamp) + src_path = os.path.join( + LOCAL_DEVICE_REPORTS.replace("{device_folder}", device.device_folder), + timestamp) # Define temp directory to store files before zipping results_dir = os.path.join(f"/tmp/testrun/{time.time()}") # Define where to save the zip file - zip_location = os.path.join("/tmp/testrun", - timestamp) + zip_location = os.path.join("/tmp/testrun", timestamp) # Delete zip_temp if it already exists if os.path.exists(results_dir): @@ -306,16 +362,13 @@ def zip_results(self, if os.path.exists(zip_location + ".zip"): os.remove(zip_location + ".zip") - shutil.copytree(src_path,results_dir) + shutil.copytree(src_path, results_dir) # Include profile if specified if profile is not None: - LOGGER.debug( - f"Copying profile {profile.name} to results directory") + LOGGER.debug(f"Copying profile {profile.name} to results directory") shutil.copy(profile.get_file_path(), - os.path.join( - results_dir, - "profile.json")) + os.path.join(results_dir, "profile.json")) with open(os.path.join(results_dir, "profile.pdf"), "wb") as f: f.write(profile.to_pdf(device).getvalue()) @@ -328,14 +381,13 @@ def zip_results(self, # Check that the ZIP was successfully created zip_file = zip_location + ".zip" - LOGGER.info(f'''Archive {'created at ' + zip_file + LOGGER.info(f"""Archive {"created at " + zip_file if os.path.exists(zip_file) - else'creation failed'}''') - + else "creation failed"}""") return zip_file - except Exception as error: # pylint: disable=W0703 + except Exception as error: # pylint: disable=W0703 LOGGER.error("Failed to create zip file") LOGGER.debug(error) return None @@ -362,25 +414,28 @@ def _run_test_module(self, module): """Start the test container and extract the results.""" # Check that Testrun is not stopping - if self.get_session().get_status() != "In Progress": + if self.get_session().get_status() != TestrunStatus.IN_PROGRESS: return - device = self._session.get_target_device() + device = self.get_session().get_target_device() LOGGER.info(f"Running test module {module.name}") # Get all tests to be executed and set to in progress - for current_test,test in enumerate(module.tests): + for current_test, test in enumerate(module.tests): # Check that device is connected if not self._net_orc.is_device_connected(): LOGGER.error("Device was disconnected") self._set_test_modules_error(current_test) - self._session.set_status("Cancelled") + self.get_session().set_status(TestrunStatus.CANCELLED) return + # Copy the test so we don't alter the source test_copy = copy.deepcopy(test) - test_copy.result = "In Progress" + + # Update test status to in progress + test_copy.result = TestResult.IN_PROGRESS # We don't want steps to resolve for in progress tests if hasattr(test_copy, "recommendations"): @@ -388,76 +443,8 @@ def _run_test_module(self, module): self.get_session().add_test_result(test_copy) - try: - - device_test_dir = os.path.join(self._root_path, RUNTIME_TEST_DIR, - device.mac_addr.replace(":", "")) - - container_runtime_dir = os.path.join(device_test_dir, module.name) - os.makedirs(container_runtime_dir, exist_ok=True) - - config_file = os.path.join(self._root_path, "local/system.json") - root_certs_dir = os.path.join(self._root_path, "local/root_certs") - - container_log_file = os.path.join(container_runtime_dir, "module.log") - - network_runtime_dir = os.path.join(self._root_path, "runtime/network") - - device_startup_capture = os.path.join(device_test_dir, "startup.pcap") - util.run_command(f"chown -R {self._host_user} {device_startup_capture}") - - device_monitor_capture = os.path.join(device_test_dir, "monitor.pcap") - util.run_command(f"chown -R {self._host_user} {device_monitor_capture}") - - client = docker.from_env() - - module.container = client.containers.run( - module.image_name, - auto_remove=True, - cap_add=["NET_ADMIN"], - name=module.container_name, - hostname=module.container_name, - privileged=True, - detach=True, - mounts=[ - Mount(target="/testrun/system.json", - source=config_file, - type="bind", - read_only=True), - Mount(target="/testrun/root_certs", - source=root_certs_dir, - type="bind", - read_only=True), - Mount(target="/runtime/output", - source=container_runtime_dir, - type="bind"), - Mount(target="/runtime/network", - source=network_runtime_dir, - type="bind", - read_only=True), - Mount(target="/runtime/device/startup.pcap", - source=device_startup_capture, - type="bind", - read_only=True), - Mount(target="/runtime/device/monitor.pcap", - source=device_monitor_capture, - type="bind", - read_only=True) - ], - environment={ - "TZ": self.get_session().get_timezone(), - "HOST_USER": self._host_user, - "DEVICE_MAC": device.mac_addr, - "IPV4_ADDR": device.ip_addr, - "DEVICE_TEST_MODULES": json.dumps(device.test_modules), - "IPV4_SUBNET": self._net_orc.network_config.ipv4_network, - "IPV6_SUBNET": self._net_orc.network_config.ipv6_network - }) - except (docker.errors.APIError, - docker.errors.ContainerError) as container_error: - LOGGER.error("Test module " + module.name + " has failed to start") - LOGGER.debug(container_error) - return + # Start the test module + module.start(device) # Mount the test container to the virtual network if requried if module.network: @@ -466,7 +453,6 @@ def _run_test_module(self, module): # Determine the module timeout time test_module_timeout = time.time() + module.timeout - status = self._get_module_status(module) # Resolving container logs is blocking so we need to spawn a new thread log_stream = module.container.logs(stream=True, stdout=True, stderr=True) @@ -475,29 +461,27 @@ def _run_test_module(self, module): log_thread.daemon = True log_thread.start() - while (status == "running" and self._session.get_status() == "In Progress"): + while (module.get_status() == "running" + and self.get_session().get_status() == TestrunStatus.IN_PROGRESS): + + # Check that timeout has not exceeded if time.time() > test_module_timeout: LOGGER.error("Module timeout exceeded, killing module: " + module.name) - self._stop_module(module=module, kill=True) + module.stop(kill=True) break - status = self._get_module_status(module) # Save all container logs to file - with open(container_log_file, "w", encoding="utf-8") as f: + with open(module.container_log_file, "w", encoding="utf-8") as f: for line in self._container_logs: f.write(line + "\n") # Check that Testrun has not been stopped whilst this module was running - if self.get_session().get_status() == "Stopping": + if self.get_session().get_status() == TestrunStatus.STOPPING: # Discard results for this module LOGGER.info(f"Test module {module.name} has forcefully quit") return - # Get test results from module - container_runtime_dir = os.path.join( - self._root_path, - "runtime/test/" + device.mac_addr.replace(":", "") + "/" + module.name) - results_file = f"{container_runtime_dir}/{module.name}-result.json" + results_file = f"{module.container_runtime_dir}/{module.name}-result.json" try: with open(results_file, "r", encoding="utf-8-sig") as f: @@ -509,59 +493,54 @@ def _run_test_module(self, module): # Convert dict from json into TestCase object test_case = TestCase( - name=test_result["name"], - description=test_result["description"], - expected_behavior=test_result["expected_behavior"], - required_result=test_result["required_result"], - result=test_result["result"]) - - # Any informational test should always report informational - if test_case.required_result == "Informational": - test_case.result = "Informational" + name=test_result["name"], + result=test_result["result"], + description=test_result["description"]) # Add steps to resolve if test is non-compliant - if (test_case.result == "Non-Compliant" and + if (test_case.result == TestResult.NON_COMPLIANT and "recommendations" in test_result): test_case.recommendations = test_result["recommendations"] else: - test_case.recommendations = None + test_case.recommendations = [] - self._session.add_test_result(test_case) + self.get_session().add_test_result(test_case) except (FileNotFoundError, PermissionError, json.JSONDecodeError) as results_error: LOGGER.error( - f"Error occurred whilst obtaining results for module {module.name}") + f"Error occurred whilst obtaining results for module {module.name}") LOGGER.error(results_error) # Get the markdown report from the module if generated - markdown_file = f"{container_runtime_dir}/{module.name}_report.md" + markdown_file = f"{module.container_runtime_dir}/{module.name}_report.md" try: with open(markdown_file, "r", encoding="utf-8") as f: module_report = f.read() - self._session.add_module_report(module_report) + self.get_session().add_module_report(module_report) except (FileNotFoundError, PermissionError): LOGGER.debug("Test module did not produce a markdown module report") # Get the HTML report from the module if generated - html_file = f"{container_runtime_dir}/{module.name}_report.html" + html_file = f"{module.container_runtime_dir}/{module.name}_report.html" try: with open(html_file, "r", encoding="utf-8") as f: module_report = f.read() LOGGER.debug(f"Adding module report for module {module.name}") - self._session.add_module_report(module_report) + self.get_session().add_module_report(module_report) except (FileNotFoundError, PermissionError): LOGGER.debug("Test module did not produce a html module report") LOGGER.info(f"Test module {module.name} has finished") - # Resolve all current log data in the containers log_stream - # this method is blocking so should be called in - # a thread or within a proper blocking context def _get_container_logs(self, log_stream): + """Resolve all current log data in the containers log_stream + this method is blocking so should be called in + a thread or within a proper blocking context""" self._container_logs = [] for log_chunk in log_stream: lines = log_chunk.decode("utf-8").splitlines() + # Process each line and strip blank space processed_lines = [line.strip() for line in lines if line.strip()] self._container_logs.extend(processed_lines) @@ -595,12 +574,32 @@ def _get_module_container(self, module): LOGGER.error(error) return container + def _load_test_packs(self): + + for test_pack_file in os.listdir(TEST_PACKS_DIR): + + LOGGER.debug(f"Loading test pack {test_pack_file}") + + with open(os.path.join( + self._root_path, + TEST_PACKS_DIR, + test_pack_file), encoding="utf-8") as f: + test_pack_json = json.load(f) + + test_pack: TestPack = TestPack( + name = test_pack_json["name"], + tests = test_pack_json["tests"], + language = test_pack_json["language"] + ) + + self._test_packs.append(test_pack) + def _load_test_modules(self): """Load network modules from module_config.json.""" LOGGER.debug("Loading test modules from /" + TEST_MODULES_DIR) loaded_modules = "Loaded the following test modules: " - test_modules_dir = os.path.join(self._path, TEST_MODULES_DIR) + test_modules_dir = os.path.join(self._root_path, TEST_MODULES_DIR) module_dirs = os.listdir(test_modules_dir) # Check if the directory protocol exists and move it to the beginning @@ -620,87 +619,39 @@ def _load_test_modules(self): def _load_test_module(self, module_dir): """Import module configuration from module_config.json.""" - LOGGER.debug(f"Loading test module {module_dir}") + # Resolve the main docker interface (docker0) for host interaction + # Can't use device or internet iface since these are not in a stable + # state for this type of communication during testing but docker0 has + # to exist and should always be available + external_ip = self._net_orc.get_ip_address("docker0") + extra_hosts = { + "external.localhost": external_ip + } if external_ip is not None else {} - modules_dir = os.path.join(self._path, TEST_MODULES_DIR) + # Make sure we only load each module once since some modules will + # depend on the same module + if not any(m.dir_name == module_dir for m in self._test_modules): - # Load basic module information - module = TestModule() - with open(os.path.join(self._path, modules_dir, module_dir, MODULE_CONFIG), - encoding="UTF-8") as module_config_file: - module_json = json.load(module_config_file) + modules_dir = os.path.join(self._root_path, TEST_MODULES_DIR) - module.name = module_json["config"]["meta"]["name"] - module.display_name = module_json["config"]["meta"]["display_name"] - module.description = module_json["config"]["meta"]["description"] + module_conf_file = os.path.join(self._root_path, modules_dir, module_dir, + MODULE_CONFIG) - if "enabled" in module_json["config"]: - module.enabled = module_json["config"]["enabled"] + module = TestModule(module_conf_file, self.get_session(), extra_hosts) + if module.depends_on is not None: + self._load_test_module(module.depends_on) + self._test_modules.append(module) - module.dir = os.path.join(self._path, modules_dir, module_dir) - module.dir_name = module_dir - module.build_file = module_dir + ".Dockerfile" - module.container_name = "tr-ct-" + module.dir_name + "-test" - module.image_name = "test-run/" + module.dir_name + "-test" + return module - # Load test cases - if "tests" in module_json["config"]: - module.total_tests = len(module_json["config"]["tests"]) - for test_case_json in module_json["config"]["tests"]: - try: - test_case = TestCase( - name=test_case_json["name"], - description=test_case_json["test_description"], - expected_behavior=test_case_json["expected_behavior"], - required_result=test_case_json["required_result"] - ) + def get_test_packs(self) -> List[TestPack]: + return self._test_packs - if "recommendations" in test_case_json: - test_case.recommendations = test_case_json["recommendations"] - module.tests.append(test_case) - except Exception as error: # pylint: disable=W0718 - LOGGER.error("Failed to load test case. See error for details") - LOGGER.error(error) - - if "timeout" in module_json["config"]["docker"]: - module.timeout = module_json["config"]["docker"]["timeout"] - - # Determine if this is a container or just an image/template - if "enable_container" in module_json["config"]["docker"]: - module.enable_container = module_json["config"]["docker"][ - "enable_container"] - - # Determine if this module needs network access - if "network" in module_json["config"]: - module.network = module_json["config"]["network"] - - # Ensure container is built after any dependencies - if "depends_on" in module_json["config"]["docker"]: - depends_on_module = module_json["config"]["docker"]["depends_on"] - if self._get_test_module(depends_on_module) is None: - self._load_test_module(depends_on_module) - - self._test_modules.append(module) - return module - - def build_test_modules(self): - """Build all test modules.""" - LOGGER.info("Building test modules...") - for module in self._test_modules: - self._build_test_module(module) - - def _build_test_module(self, module): - LOGGER.debug("Building docker image for module " + module.dir_name) - - client = docker.from_env() - try: - client.images.build( - dockerfile=os.path.join(module.dir, module.build_file), - path=self._path, - forcerm=True, # Cleans up intermediate containers during build - tag=module.image_name) - except docker.errors.BuildError as error: - LOGGER.error(error) + def get_test_pack(self, name: str) -> TestPack: + for test_pack in self._test_packs: + if test_pack.name.lower() == name.lower(): + return test_pack + return None def _stop_modules(self, kill=False): LOGGER.info("Stopping test modules") @@ -713,18 +664,7 @@ def _stop_modules(self, kill=False): def _stop_module(self, module, kill=False): LOGGER.debug("Stopping test module " + module.container_name) - try: - container = module.container - if container is not None: - if kill: - LOGGER.debug("Killing container:" + module.container_name) - container.kill() - else: - LOGGER.debug("Stopping container:" + module.container_name) - container.stop() - LOGGER.debug("Container stopped:" + module.container_name) - except docker.errors.NotFound: - pass + module.stop(kill=kill) def get_test_modules(self): return self._test_modules @@ -759,4 +699,3 @@ def _set_test_modules_error(self, current_test): self.get_session().set_test_result_error( self._test_modules_running[i].tests[j] ) - diff --git a/framework/python/src/test_orc/test_pack.py b/framework/python/src/test_orc/test_pack.py new file mode 100644 index 000000000..b1e3d5d3c --- /dev/null +++ b/framework/python/src/test_orc/test_pack.py @@ -0,0 +1,42 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Represents a testing pack.""" +from typing import List, Dict +from dataclasses import dataclass, field +from collections import defaultdict + + +@dataclass +class TestPack: # pylint: disable=too-few-public-methods,too-many-instance-attributes + """Represents a test pack.""" + + name: str = "undefined" + description: str = "" + tests: List[dict] = field(default_factory=lambda: []) + language: Dict = field(default_factory=lambda: defaultdict(dict)) + + def get_required_result(self, test_name: str) -> str: + + for test in self.tests: + if "name" in test and test["name"].lower() == test_name.lower(): + if "required_result" in test: + return test["required_result"] + + return "Informational" + + def get_message(self, name: str) -> str: + if name in self.language: + return self.language[name] + return "Message not found" diff --git a/framework/requirements.txt b/framework/requirements.txt index 0484905ee..fab17b071 100644 --- a/framework/requirements.txt +++ b/framework/requirements.txt @@ -39,3 +39,6 @@ paho-mqtt==2.1.0 # Requirements for background tasks APScheduler==3.10.4 + +# Requirements for reports generation +Jinja2==3.1.4 diff --git a/local/system.json.example b/local/system.json.example index 23023bead..df89b502f 100644 --- a/local/system.json.example +++ b/local/system.json.example @@ -1,10 +1,11 @@ { "network": { - "device_intf": "enx123456789123", - "internet_intf": "enx123456789124" + "device_intf": "", + "internet_intf": "" }, "log_level": "INFO", "startup_timeout": 60, "monitor_period": 300, - "max_device_reports": 0 + "max_device_reports": 0, + "org_name": "" } diff --git a/make/DEBIAN/control b/make/DEBIAN/control index cecad9d17..31922abde 100644 --- a/make/DEBIAN/control +++ b/make/DEBIAN/control @@ -1,5 +1,5 @@ Package: Testrun -Version: 1.4-a +Version: 2.0 Architecture: amd64 Maintainer: Google Homepage: https://github.com/google/testrun diff --git a/modules/devices/faux-dev/bin/start_network_service b/modules/devices/faux-dev/bin/start_network_service index d4bb8a92d..7d689f9dd 100644 --- a/modules/devices/faux-dev/bin/start_network_service +++ b/modules/devices/faux-dev/bin/start_network_service @@ -35,7 +35,7 @@ else INTF=$DEFINED_IFACE fi -#Create and set permissions on the output files +# Create and set permissions on the output files OUTPUT_DIR=/runtime/validation/ LOG_FILE=$OUTPUT_DIR/$MODULE_NAME.log RESULT_FILE=$OUTPUT_DIR/result.json diff --git a/modules/devices/faux-dev/faux-dev.Dockerfile b/modules/devices/faux-dev/faux-dev.Dockerfile index ecfdfc5c2..18901a2a1 100644 --- a/modules/devices/faux-dev/faux-dev.Dockerfile +++ b/modules/devices/faux-dev/faux-dev.Dockerfile @@ -12,13 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/faux-dev -FROM test-run/base:latest +# Image name: testrun/faux-dev +FROM testrun/base:latest ARG MODULE_NAME=faux-dev ARG MODULE_DIR=modules/devices/$MODULE_NAME +ARG COMMON_DIR=framework/python/src/common -#Update and get all additional requirements not contained in the base image +# Update and get all additional requirements not contained in the base image RUN apt-get update --fix-missing # NTP requireds interactive installation so we're going to turn that off @@ -34,4 +35,4 @@ COPY $MODULE_DIR/conf /testrun/conf COPY $MODULE_DIR/bin /testrun/bin # Copy over all python files -COPY $MODULE_DIR/python /testrun/python \ No newline at end of file +COPY $MODULE_DIR/python /testrun/python diff --git a/modules/devices/faux-dev/python/src/logger.py b/modules/devices/faux-dev/python/src/logger.py deleted file mode 100644 index a727ad7bb..000000000 --- a/modules/devices/faux-dev/python/src/logger.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Sets up the logger to be used for the faux-device.""" - -import json -import logging -import os - -LOGGERS = {} -_LOG_FORMAT = '%(asctime)s %(name)-8s %(levelname)-7s %(message)s' -_DATE_FORMAT = '%b %02d %H:%M:%S' -_CONF_DIR = 'conf' -_CONF_FILE_NAME = 'system.json' -_LOG_DIR = '/runtime/validation' - -# Set log level -with open(os.path.join(_CONF_DIR, _CONF_FILE_NAME), - encoding='utf-8') as conf_file: - system_conf_json = json.load(conf_file) - -log_level_str = system_conf_json['log_level'] -log_level = logging.getLevelName(log_level_str) - -log_format = logging.Formatter(fmt=_LOG_FORMAT, datefmt=_DATE_FORMAT) - - -def add_file_handler(log, log_file): - """Add file handler to existing log.""" - handler = logging.FileHandler(os.path.join(_LOG_DIR, log_file + '.log')) - handler.setFormatter(log_format) - log.addHandler(handler) - - -def add_stream_handler(log): - """Add stream handler to existing log.""" - handler = logging.StreamHandler() - handler.setFormatter(log_format) - log.addHandler(handler) - - -def get_logger(name, log_file=None): - """Return logger for requesting class.""" - if name not in LOGGERS: - LOGGERS[name] = logging.getLogger(name) - LOGGERS[name].setLevel(log_level) - add_stream_handler(LOGGERS[name]) - if log_file is not None: - add_file_handler(LOGGERS[name], log_file) - return LOGGERS[name] diff --git a/modules/devices/faux-dev/python/src/util.py b/modules/devices/faux-dev/python/src/util.py deleted file mode 100644 index 81f9d2ced..000000000 --- a/modules/devices/faux-dev/python/src/util.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Provides basic utilities for the faux-device.""" -import subprocess -import shlex - - -def run_command(cmd, logger, output=True): - """Runs a process at the os level - By default, returns the standard output and error output - If the caller sets optional output parameter to False, - will only return a boolean result indicating if it was - successful in running the command. Failure is indicated - by any return code from the process other than zero.""" - - success = False - with subprocess.Popen( - shlex.split(cmd), - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) as process: - - stdout, stderr = process.communicate() - - if process.returncode != 0: - err_msg = f'{stderr.strip()}. Code: {process.returncode}' - logger.error('Command Failed: ' + cmd) - logger.error('Error: ' + err_msg) - else: - success = True - logger.debug('Command succeeded: ' + cmd) - if output: - out = stdout.strip().decode('utf-8') - logger.debug('Command output: ' + out) - return success, out - else: - return success, None diff --git a/modules/network/base/base.Dockerfile b/modules/network/base/base.Dockerfile index b30f6a7d9..7f6edb409 100644 --- a/modules/network/base/base.Dockerfile +++ b/modules/network/base/base.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/base -FROM ubuntu@sha256:e6173d4dc55e76b87c4af8db8821b1feae4146dd47341e4d431118c7dd060a74 +# Image name: testrun/base +FROM ubuntu@sha256:77d57fd89366f7d16615794a5b53e124d742404e20f035c22032233f1826bd6a RUN apt-get update @@ -30,8 +30,9 @@ COPY $COMMON_DIR/ /testrun/python/src/common # Setup the base python requirements COPY $MODULE_DIR/python /testrun/python -# Install all python requirements for the module -RUN pip3 install -r /testrun/python/requirements.txt +# Install all python requirements for the module +# --break-system-packages flag used to bypass PEP668 +RUN pip3 install --break-system-packages -r /testrun/python/requirements.txt # Add the bin files COPY $MODULE_DIR/bin /testrun/bin @@ -42,5 +43,5 @@ RUN dos2unix /testrun/bin/* # Make sure all the bin files are executable RUN chmod u+x /testrun/bin/* -#Start the network module +# Start the network module ENTRYPOINT [ "/testrun/bin/start_module" ] \ No newline at end of file diff --git a/modules/network/base/bin/start_module b/modules/network/base/bin/start_module index 8e8cb5e4b..3b1c092cd 100644 --- a/modules/network/base/bin/start_module +++ b/modules/network/base/bin/start_module @@ -42,6 +42,7 @@ fi # Extract the necessary config parameters MODULE_NAME=$(echo "$CONF" | jq -r '.config.meta.name') DEFINED_IFACE=$(echo "$CONF" | jq -r '.config.network.interface') +HOST=$(echo "$CONF" | jq -r '.config.network.host') GRPC=$(echo "$CONF" | jq -r '.config.grpc') # Validate the module name is present @@ -70,14 +71,19 @@ $BIN_DIR/setup_binaries $BIN_DIR echo "Starting module $MODULE_NAME on local interface $INTF..." -# Wait for interface to become ready -$BIN_DIR/wait_for_interface $INTF +# Only non-host containers will have a specific +# interface for capturing +if [[ "$HOST" != "true" ]]; then -# Small pause to let the interface stabalize before starting the capture -#sleep 1 + # Wait for interface to become ready + $BIN_DIR/wait_for_interface $INTF -# Start network capture -$BIN_DIR/capture $MODULE_NAME $INTF + # Small pause to let the interface stabalize before starting the capture + #sleep 1 + + # Start network capture + $BIN_DIR/capture $MODULE_NAME $INTF +fi # Start the grpc server if [[ ! -z $GRPC && ! $GRPC == "null" ]] @@ -96,4 +102,4 @@ fi sleep 3 # Start the networking service -$BIN_DIR/start_network_service $MODULE_NAME $INTF \ No newline at end of file +$BIN_DIR/start_network_service $MODULE_NAME $INTF diff --git a/modules/network/base/python/src/grpc_server/start_server.py b/modules/network/base/python/src/grpc_server/start_server.py index d372949e5..9c34ec736 100644 --- a/modules/network/base/python/src/grpc_server/start_server.py +++ b/modules/network/base/python/src/grpc_server/start_server.py @@ -46,6 +46,5 @@ def run(): print('gRPC server starting on port ' + port) serve(port) - if __name__ == '__main__': run() diff --git a/modules/network/dhcp-1/dhcp-1.Dockerfile b/modules/network/dhcp-1/dhcp-1.Dockerfile index e50ed9a95..7df94b4fd 100644 --- a/modules/network/dhcp-1/dhcp-1.Dockerfile +++ b/modules/network/dhcp-1/dhcp-1.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/dhcp-primary -FROM test-run/base:latest +# Image name: testrun/dhcp-primary +FROM testrun/base:latest ARG MODULE_NAME=dhcp-1 ARG MODULE_DIR=modules/network/$MODULE_NAME diff --git a/modules/network/dhcp-2/dhcp-2.Dockerfile b/modules/network/dhcp-2/dhcp-2.Dockerfile index 66ea857c3..4dcd7a819 100644 --- a/modules/network/dhcp-2/dhcp-2.Dockerfile +++ b/modules/network/dhcp-2/dhcp-2.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/dhcp-primary -FROM test-run/base:latest +# Image name: testrun/dhcp-primary +FROM testrun/base:latest ARG MODULE_NAME=dhcp-2 ARG MODULE_DIR=modules/network/$MODULE_NAME diff --git a/modules/network/dns/dns.Dockerfile b/modules/network/dns/dns.Dockerfile index d59b8a391..2b46dfb4a 100644 --- a/modules/network/dns/dns.Dockerfile +++ b/modules/network/dns/dns.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/dns -FROM test-run/base:latest +# Image name: testrun/dns +FROM testrun/base:latest ARG MODULE_NAME=dns ARG MODULE_DIR=modules/network/$MODULE_NAME diff --git a/modules/network/gateway/gateway.Dockerfile b/modules/network/gateway/gateway.Dockerfile index 885e4a9f0..2b72174ab 100644 --- a/modules/network/gateway/gateway.Dockerfile +++ b/modules/network/gateway/gateway.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/gateway -FROM test-run/base:latest +# Image name: testrun/gateway +FROM testrun/base:latest ARG MODULE_NAME=gateway ARG MODULE_DIR=modules/network/$MODULE_NAME diff --git a/modules/network/host/bin/start_network_service b/modules/network/host/bin/start_network_service new file mode 100644 index 000000000..b94b6ff7c --- /dev/null +++ b/modules/network/host/bin/start_network_service @@ -0,0 +1,22 @@ +#!/bin/bash + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +echo "Starting host service..." + +# Keep host container running until stopped +while true; do + sleep 3 +done diff --git a/modules/network/host/conf/module_config.json b/modules/network/host/conf/module_config.json new file mode 100644 index 000000000..87ec39a35 --- /dev/null +++ b/modules/network/host/conf/module_config.json @@ -0,0 +1,24 @@ +{ + "config": { + "meta": { + "name": "host", + "display_name": "Host", + "description": "Used to access host level networking operations" + }, + "network": { + "host": true + }, + "grpc":{ + "port": 5001 + }, + "docker": { + "depends_on": "base", + "mounts": [ + { + "source": "runtime/network", + "target": "/runtime/network" + } + ] + } + } +} \ No newline at end of file diff --git a/modules/network/host/host.Dockerfile b/modules/network/host/host.Dockerfile new file mode 100644 index 000000000..60c8bf59a --- /dev/null +++ b/modules/network/host/host.Dockerfile @@ -0,0 +1,34 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Image name: testrun/host +FROM testrun/base:latest + +ARG MODULE_NAME=host +ARG MODULE_DIR=modules/network/$MODULE_NAME + +#Update and get all additional requirements not contained in the base image +RUN apt-get update --fix-missing + +# Install all necessary packages +RUN apt-get install -y net-tools ethtool + +# Copy over all configuration files +COPY $MODULE_DIR/conf /testrun/conf + +# Copy over all binary files +COPY $MODULE_DIR/bin /testrun/bin + +# Copy over all python files +COPY $MODULE_DIR/python /testrun/python diff --git a/modules/network/host/python/src/grpc_server/network_service.py b/modules/network/host/python/src/grpc_server/network_service.py new file mode 100644 index 000000000..cbb3a1b7a --- /dev/null +++ b/modules/network/host/python/src/grpc_server/network_service.py @@ -0,0 +1,120 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""gRPC Network Service for the Host network module""" +import proto.grpc_pb2_grpc as pb2_grpc +import proto.grpc_pb2 as pb2 + +import traceback +from common import logger +from common import util + +LOG_NAME = 'network_service' +LOGGER = None + + +class NetworkService(pb2_grpc.HostNetworkModule): + """gRPC endpoints for the Host container""" + + def __init__(self): + global LOGGER + LOGGER = logger.get_logger(LOG_NAME, 'host') + + def CheckInterfaceStatus(self, request, context): # pylint: disable=W0613 + try: + status = self.check_interface_status(request.iface_name) + return pb2.CheckInterfaceStatusResponse(code=200, status=status) + except Exception as e: # pylint: disable=W0718 + fail_message = 'Failed to read iface status: ' + str(e) + LOGGER.error(fail_message) + LOGGER.error(traceback.format_exc()) + return pb2.CheckInterfaceStatusResponse(code=500, status=False) + + def GetIfaceConnectionStats(self, request, context): # pylint: disable=W0613 + try: + stats = self.get_iface_connection_stats(request.iface_name) + return pb2.GetIfaceStatsResponse(code=200, stats=stats) + except Exception as e: # pylint: disable=W0718 + fail_message = 'Failed to read connection stats: ' + str(e) + LOGGER.error(fail_message) + LOGGER.error(traceback.format_exc()) + return pb2.GetIfaceStatsResponse(code=500, stats=False) + + def GetIfacePortStats(self, request, context): # pylint: disable=W0613 + try: + stats = self.get_iface_port_stats(request.iface_name) + return pb2.GetIfaceStatsResponse(code=200, stats=stats) + except Exception as e: # pylint: disable=W0718 + fail_message = 'Failed to read port stats: ' + str(e) + LOGGER.error(fail_message) + LOGGER.error(traceback.format_exc()) + return pb2.GetIfaceStatsResponse(code=500, stats=False) + + def SetIfaceDown(self, request, context): # pylint: disable=W0613 + try: + success = self.set_interface_down(request.iface_name) + return pb2.SetIfaceResponse(code=200, success=success) + except Exception as e: # pylint: disable=W0718 + fail_message = 'Failed to set interface down: ' + str(e) + LOGGER.error(fail_message) + LOGGER.error(traceback.format_exc()) + return pb2.SetIfaceResponse(code=500, success=False) + + def SetIfaceUp(self, request, context): # pylint: disable=W0613 + try: + success = self.set_interface_up(request.iface_name) + return pb2.SetIfaceResponse(code=200, success=success) + except Exception as e: # pylint: disable=W0718 + fail_message = 'Failed to set interface up: ' + str(e) + LOGGER.error(fail_message) + LOGGER.error(traceback.format_exc()) + return pb2.SetIfaceResponse(code=500, success=False) + + def check_interface_status(self, interface_name): + output = util.run_command(cmd=f'ip link show {interface_name}', output=True) + if 'state DOWN ' in output[0]: + return False + else: + return True + + def get_iface_connection_stats(self, iface): + """Extract information about the physical connection""" + response = util.run_command(f'ethtool {iface}') + if len(response[1]) == 0: + return response[0] + else: + return None + + def get_iface_port_stats(self, iface): + """Extract information about packets connection""" + response = util.run_command(f'ethtool -S {iface}') + if len(response[1]) == 0: + return response[0] + else: + return None + + def set_interface_up(self, interface_name): + """Set the interface to the up state""" + response = util.run_command('ip link set dev ' + interface_name + ' up') + if len(response[1]) == 0: + return response[0] + else: + return None + + def set_interface_down(self, interface_name): + """Set the interface to the up state""" + response = util.run_command('ip link set dev ' + interface_name + ' down') + if len(response[1]) == 0: + return response[0] + else: + return None diff --git a/modules/network/host/python/src/grpc_server/proto/grpc.proto b/modules/network/host/python/src/grpc_server/proto/grpc.proto new file mode 100644 index 000000000..c881b13f7 --- /dev/null +++ b/modules/network/host/python/src/grpc_server/proto/grpc.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; + +service HostNetworkModule { + + rpc CheckInterfaceStatus(CheckInterfaceStatusRequest) returns (CheckInterfaceStatusResponse) {}; + rpc GetIfaceConnectionStats(GetIfaceStatsRequest) returns (GetIfaceStatsResponse) {}; + rpc SetIfaceDown(SetIfaceRequest) returns (SetIfaceResponse) {}; + rpc SetIfaceUp(SetIfaceRequest) returns (SetIfaceResponse) {}; +} + +message CheckInterfaceStatusRequest { + string iface_name = 1; +} + +message CheckInterfaceStatusResponse { + int32 code = 1; + bool status = 2; +} + +message GetIfaceStatsRequest { + string iface_name = 1; +} + +message GetIfaceStatsResponse { + int32 code = 1; + string stats = 2; +} + +message SetIfaceRequest { + string iface_name = 1; +} + +message SetIfaceResponse { + int32 code = 1; + bool success = 2; +} + diff --git a/modules/network/host/python/src/grpc_server/start_server.py b/modules/network/host/python/src/grpc_server/start_server.py new file mode 100644 index 000000000..962277188 --- /dev/null +++ b/modules/network/host/python/src/grpc_server/start_server.py @@ -0,0 +1,50 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Base class for starting the gRPC server for a network module.""" +from concurrent import futures +import grpc +import proto.grpc_pb2_grpc as pb2_grpc +from network_service import NetworkService +import argparse + +DEFAULT_PORT = '5001' + + +def serve(port): + server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + pb2_grpc.add_HostNetworkModuleServicer_to_server(NetworkService(), server) + server.add_insecure_port('[::]:' + port) + server.start() + server.wait_for_termination() + + +def run(): + parser = argparse.ArgumentParser( + description='GRPC Server for Network Module', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('-p', + '--port', + default=DEFAULT_PORT, + help='Define the default port to run the server on.') + + args = parser.parse_args() + + port = args.port + + print('gRPC server starting on port ' + port) + serve(port) + +if __name__ == '__main__': + run() diff --git a/modules/network/ntp/ntp.Dockerfile b/modules/network/ntp/ntp.Dockerfile index aa6f63e3f..1d2a52494 100644 --- a/modules/network/ntp/ntp.Dockerfile +++ b/modules/network/ntp/ntp.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/ntp -FROM test-run/base:latest +# Image name: testrun/ntp +FROM testrun/base:latest ARG MODULE_NAME=ntp ARG MODULE_DIR=modules/network/$MODULE_NAME diff --git a/modules/network/ntp/python/src/ntp_server.py b/modules/network/ntp/python/src/ntp_server.py index 42fe21e77..fbe3ac17e 100644 --- a/modules/network/ntp/python/src/ntp_server.py +++ b/modules/network/ntp/python/src/ntp_server.py @@ -38,7 +38,7 @@ def is_running(self): if __name__ == '__main__': ntp = NTPServer() ntp.start() - # give some time for the server to start + # Give some time for the server to start running = False for _ in range(10): running = ntp.is_running() diff --git a/modules/network/radius/radius.Dockerfile b/modules/network/radius/radius.Dockerfile index 4c8f8fac5..802480ce9 100644 --- a/modules/network/radius/radius.Dockerfile +++ b/modules/network/radius/radius.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/radius -FROM test-run/base:latest +# Image name: testrun/radius +FROM testrun/base:latest ARG MODULE_NAME=radius ARG MODULE_DIR=modules/network/$MODULE_NAME @@ -25,7 +25,8 @@ RUN apt-get update && apt-get install -y openssl freeradius git RUN git clone --branch 0.0.25 https://github.com/faucetsdn/chewie # Install chewie as Python module -RUN pip3 install chewie/ +# --break-system-packages flag used to bypass PEP668 +RUN pip3 install --break-system-packages chewie/ EXPOSE 1812/udp EXPOSE 1813/udp @@ -40,4 +41,5 @@ COPY $MODULE_DIR/bin /testrun/bin COPY $MODULE_DIR/python /testrun/python # Install all python requirements for the module -RUN pip3 install -r /testrun/python/requirements.txt \ No newline at end of file +# --break-system-packages flag used to bypass PEP668 +RUN pip3 install --break-system-packages -r /testrun/python/requirements.txt \ No newline at end of file diff --git a/modules/network/template/template.Dockerfile b/modules/network/template/template.Dockerfile index 1c3060496..265725258 100644 --- a/modules/network/template/template.Dockerfile +++ b/modules/network/template/template.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/template -FROM test-run/base:latest +# Image name: testrun/template +FROM testrun/base:latest ARG MODULE_NAME=template ARG MODULE_DIR=modules/network/$MODULE_NAME diff --git a/modules/test/base/base.Dockerfile b/modules/test/base/base.Dockerfile index 4d8c0399a..cc78a934c 100644 --- a/modules/test/base/base.Dockerfile +++ b/modules/test/base/base.Dockerfile @@ -12,17 +12,23 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/base-test -FROM ubuntu@sha256:e6173d4dc55e76b87c4af8db8821b1feae4146dd47341e4d431118c7dd060a74 +# Builder stage +# Image name: testrun/base-test +FROM python:3.10-slim AS builder ARG MODULE_NAME=base ARG MODULE_DIR=modules/test/$MODULE_NAME ARG COMMON_DIR=framework/python/src/common -RUN apt-get update +# Install additional requirements needed to build python packages +RUN apt-get update && \ + apt-get install -y gcc dos2unix -# Install common software -RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq net-tools iputils-ping tzdata tcpdump iproute2 jq python3 python3-pip dos2unix nmap wget --fix-missing +# Create the virtual environment +RUN python -m venv /opt/venv + +# Activate the virtual environment +ENV PATH="/opt/venv/bin:$PATH" # Install common python modules COPY $COMMON_DIR/ /testrun/python/src/common @@ -31,7 +37,7 @@ COPY $COMMON_DIR/ /testrun/python/src/common COPY $MODULE_DIR/python /testrun/python # Install all python requirements for the module -RUN pip3 install -r /testrun/python/requirements.txt +RUN pip install -r /testrun/python/requirements.txt # Copy over all binary files COPY $MODULE_DIR/bin /testrun/bin @@ -49,6 +55,7 @@ ARG CONTAINER_PROTO_DIR=testrun/python/src/grpc_server/proto COPY $NET_MODULE_DIR/dhcp-1/$NET_MODULE_PROTO_DIR $CONTAINER_PROTO_DIR/dhcp1/ COPY $NET_MODULE_DIR/dhcp-2/$NET_MODULE_PROTO_DIR $CONTAINER_PROTO_DIR/dhcp2/ +COPY $NET_MODULE_DIR/host/$NET_MODULE_PROTO_DIR $CONTAINER_PROTO_DIR/host/ # Copy the cached version of oui.txt incase the download fails RUN mkdir -p /usr/local/etc @@ -57,5 +64,21 @@ COPY $MODULE_DIR/usr/local/etc/oui.txt /usr/local/etc/oui.txt # Update the oui.txt file from ieee RUN wget https://standards-oui.ieee.org/oui.txt -O /usr/local/etc/oui.txt || echo "Unable to update the MAC OUI database" +# Operational stage +FROM python:3.10-slim + +# Install common software +RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -yq net-tools iputils-ping tzdata tcpdump iproute2 jq dos2unix nmap wget --fix-missing + +# Get the virtual environment from builder stage +COPY --from=builder /opt/venv /opt/venv + +# Copy over all testrun files from the builder stage +COPY --from=builder /testrun /testrun +COPY --from=builder /usr/local/etc/oui.txt /usr/local/etc/oui.txt + +# Activate the virtual environment by setting the PATH +ENV PATH="/opt/venv/bin:$PATH" + # Start the test module ENTRYPOINT [ "/testrun/bin/start" ] \ No newline at end of file diff --git a/modules/test/base/bin/setup b/modules/test/base/bin/setup index 23c96c513..5b74790e5 100644 --- a/modules/test/base/bin/setup +++ b/modules/test/base/bin/setup @@ -41,7 +41,7 @@ CONF=`cat $CONF_FILE` if [[ -z $CONF ]] then - echo "No config file present at $CONF_FILE. Exiting startup." + echo "No config file present at $CONF_FILE. Exiting startup." exit 1 fi diff --git a/modules/test/base/python/src/grpc/proto/host/client.py b/modules/test/base/python/src/grpc/proto/host/client.py new file mode 100644 index 000000000..e08d3376a --- /dev/null +++ b/modules/test/base/python/src/grpc/proto/host/client.py @@ -0,0 +1,63 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License +"""gRPC client module for the secondary DHCP Server""" +import grpc +import host.grpc_pb2_grpc as pb2_grpc +import host.grpc_pb2 as pb2 + +DEFAULT_PORT = '5001' +DEFAULT_HOST = 'external.localhost' # Default DHCP2 server + + +class Client(): + """gRPC Client for the secondary DHCP server""" + def __init__(self, port=DEFAULT_PORT, host=DEFAULT_HOST): + self._port = port + self._host = host + + # Create a gRPC channel to connect to the server + self._channel = grpc.insecure_channel(self._host + ':' + self._port) + + # Create a gRPC stub + self._stub = pb2_grpc.HostNetworkModuleStub(self._channel) + + def check_interface_status(self, iface_name): + # Create a request message + request = pb2.CheckInterfaceStatusRequest() + request.iface_name = iface_name + + # Make the RPC call + response = self._stub.CheckInterfaceStatus(request) + + return response + + def set_iface_down(self, iface_name): + # Create a request message + request = pb2.SetIfaceRequest() + request.iface_name = iface_name + + # Make the RPC call + response = self._stub.SetIfaceDown(request) + + return response + + def set_iface_up(self, iface_name): + # Create a request message + request = pb2.SetIfaceRequest() + request.iface_name = iface_name + + # Make the RPC call + response = self._stub.SetIfaceUp(request) + + return response diff --git a/modules/test/base/python/src/logger.py b/modules/test/base/python/src/logger.py deleted file mode 100644 index e6a2b004c..000000000 --- a/modules/test/base/python/src/logger.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Sets up the logger to be used for the test modules.""" -import json -import logging - -LOGGERS = {} -_LOG_FORMAT = '%(asctime)s %(name)-8s %(levelname)-7s %(message)s' -_DATE_FORMAT = '%b %02d %H:%M:%S' -_DEFAULT_LEVEL = logging.INFO -_CONF_FILE_NAME = 'testrun/system.json' -_LOG_DIR = '/runtime/output/' - -# Set log level -try: - with open(_CONF_FILE_NAME, - encoding='UTF-8') as config_json_file: - system_conf_json = json.load(config_json_file) - - log_level_str = system_conf_json['log_level'] - log_level = logging.getLevelName(log_level_str) -except OSError: - # TODO: Print out warning that log level is incorrect or missing - log_level = _DEFAULT_LEVEL - -log_format = logging.Formatter(fmt=_LOG_FORMAT, datefmt=_DATE_FORMAT) - - -def add_file_handler(log, log_file, log_dir=_LOG_DIR): - handler = logging.FileHandler(log_dir + log_file + '.log') - handler.setFormatter(log_format) - log.addHandler(handler) - - -def add_stream_handler(log): - handler = logging.StreamHandler() - handler.setFormatter(log_format) - log.addHandler(handler) - - -def get_logger(name, log_file=None, log_dir=_LOG_DIR): - if name not in LOGGERS: - LOGGERS[name] = logging.getLogger(name) - LOGGERS[name].setLevel(log_level) - add_stream_handler(LOGGERS[name]) - if log_file is not None: - log_dir = log_dir if log_dir is not None else _LOG_DIR - add_file_handler(LOGGERS[name], log_file, log_dir=log_dir) - return LOGGERS[name] diff --git a/modules/test/base/python/src/test_module.py b/modules/test/base/python/src/test_module.py index deed0d978..1487fb786 100644 --- a/modules/test/base/python/src/test_module.py +++ b/modules/test/base/python/src/test_module.py @@ -19,6 +19,8 @@ from datetime import datetime import traceback +from common.statuses import TestResult + LOGGER = None RESULTS_DIR = '/runtime/output/' CONF_FILE = '/testrun/conf/module_config.json' @@ -39,6 +41,7 @@ def __init__(self, self._ipv4_addr = os.environ.get('IPV4_ADDR', '') self._ipv4_subnet = os.environ.get('IPV4_SUBNET', '') self._ipv6_subnet = os.environ.get('IPV6_SUBNET', '') + self._dev_iface_mac = os.environ.get('DEV_IFACE_MAC', '') self._add_logger(log_name=log_name, module_name=module_name, log_dir=log_dir) @@ -116,25 +119,25 @@ def run_tests(self): LOGGER.error(e) traceback.print_exc() else: - LOGGER.info(f'Test {test["name"]} not implemented. Skipping') - test['result'] = 'Error' - test['description'] = 'This test could not be found' + LOGGER.error(f'Test {test["name"]} has not been implemented') + result = TestResult.ERROR, 'This test could not be found' else: LOGGER.debug(f'Test {test["name"]} is disabled') + result = (TestResult.DISABLED, + 'This test did not run because it is disabled') - # To be added in v1.3.2 - # result = 'Disabled', 'This test is disabled and did not run' - + # Check if the test module has returned a result if result is not None: + # Compliant or non-compliant as a boolean only if isinstance(result, bool): - test['result'] = 'Compliant' if result else 'Non-Compliant' + test['result'] = (TestResult.COMPLIANT + if result else TestResult.NON_COMPLIANT) test['description'] = 'No description was provided for this test' else: - # TODO: This is assuming that result is an array but haven't checked # Error result if result[0] is None: - test['result'] = 'Error' + test['result'] = TestResult.ERROR if len(result) > 1: test['description'] = result[1] else: @@ -142,13 +145,14 @@ def run_tests(self): # Compliant / Non-Compliant result elif isinstance(result[0], bool): - test['result'] = 'Compliant' if result[0] else 'Non-Compliant' + test['result'] = (TestResult.COMPLIANT + if result[0] else TestResult.NON_COMPLIANT) # Result may be a string, e.g Error, Feature Not Detected elif isinstance(result[0], str): test['result'] = result[0] else: LOGGER.error(f'Unknown result detected: {result[0]}') - test['result'] = 'Error' + test['result'] = TestResult.ERROR # Check that description is a string if isinstance(result[1], str): @@ -159,12 +163,17 @@ def run_tests(self): # Check if details were provided if len(result)>2: test['details'] = result[2] + + # Check if tags were provided + if len(result)>3: + test['tags'] = result[3] else: - test['result'] = 'Error' + LOGGER.debug('No result was returned from the test module') + test['result'] = TestResult.ERROR test['description'] = 'An error occured whilst running this test' # Remove the steps to resolve if compliant already - if (test['result'] == 'Compliant' and 'recommendations' in test): + if (test['result'] == TestResult.COMPLIANT and 'recommendations' in test): test.pop('recommendations') test['end'] = datetime.now().isoformat() diff --git a/modules/test/base/python/src/util.py b/modules/test/base/python/src/util.py deleted file mode 100644 index 006648037..000000000 --- a/modules/test/base/python/src/util.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Provides basic utilities for a test module.""" -import subprocess -import shlex -import logger - -LOGGER = logger.get_logger('util') - -def run_command(cmd, output=True): - """Runs a process at the os level - By default, returns the standard output and error output - If the caller sets optional output parameter to False, - will only return a boolean result indicating if it was - successful in running the command. Failure is indicated - by any return code from the process other than zero.""" - - success = False - with subprocess.Popen(shlex.split(cmd), - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) as process: - stdout, stderr = process.communicate() - if process.returncode != 0 and output: - err_msg = f'{stderr.strip()}. Code: {process.returncode}' - LOGGER.error('Command Failed: ' + cmd) - LOGGER.error('Error: ' + err_msg) - else: - success = True - LOGGER.debug('Command succeeded: ' + cmd) - if output: - out = stdout.strip().decode('utf-8') - LOGGER.debug('Command output: ' + out) - return out, stderr - else: - return success diff --git a/modules/test/baseline/baseline.Dockerfile b/modules/test/baseline/baseline.Dockerfile index f7d21f8c8..7a83c8de2 100644 --- a/modules/test/baseline/baseline.Dockerfile +++ b/modules/test/baseline/baseline.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/baseline-test -FROM test-run/base-test:latest +# Image name: testrun/baseline-test +FROM testrun/base-test:latest ARG MODULE_NAME=baseline ARG MODULE_DIR=modules/test/$MODULE_NAME diff --git a/modules/test/baseline/conf/module_config.json b/modules/test/baseline/conf/module_config.json index f28f74a06..d120f1c71 100644 --- a/modules/test/baseline/conf/module_config.json +++ b/modules/test/baseline/conf/module_config.json @@ -16,20 +16,17 @@ { "name": "baseline.compliant", "test_description": "Simulate a compliant test", - "expected_behavior": "A compliant test result is generated", - "required_result": "Required" + "expected_behavior": "A compliant test result is generated" }, { "name": "baseline.non_compliant", "test_description": "Simulate a non-compliant test", - "expected_behavior": "A non-compliant test result is generated", - "required_result": "Recommended" + "expected_behavior": "A non-compliant test result is generated" }, { "name": "baseline.skipped", "test_description": "Simulate a skipped test", - "expected_behavior": "A skipped test result is generated", - "required_result": "Skipped" + "expected_behavior": "A skipped test result is generated" } ] } diff --git a/modules/test/conn/conf/module_config.json b/modules/test/conn/conf/module_config.json index 5289e7eb0..ca386a5e4 100644 --- a/modules/test/conn/conf/module_config.json +++ b/modules/test/conn/conf/module_config.json @@ -16,38 +16,32 @@ { "name": "connection.port_link", "test_description": "The network switch port connected to the device has an active link without errors", - "expected_behavior": "When the etherent cable is connected to the port, the device triggers the port to its enabled \"Link UP\" (LEDs illuminate on device and switch ports if present) state, and the switch shows no errors with the LEDs and when interrogated with a \"show interface\" command on most network switches.", - "required_result": "Required" + "expected_behavior": "When the etherent cable is connected to the port, the device triggers the port to its enabled \"Link UP\" (LEDs illuminate on device and switch ports if present) state, and the switch shows no errors with the LEDs and when interrogated with a \"show interface\" command on most network switches." }, { "name": "connection.port_speed", "test_description": "The network switch port connected to the device has auto-negotiated a speed that is 10 Mbps or higher", - "expected_behavior": "When the ethernet cable is connected to the port, the device autonegotiates a speed that can be checked with the \"show interface\" command on most network switches. The output of this command must also show that the \"configured speed\" is set to \"auto\".", - "required_result": "Required" + "expected_behavior": "When the ethernet cable is connected to the port, the device autonegotiates a speed that can be checked with the \"show interface\" command on most network switches. The output of this command must also show that the \"configured speed\" is set to \"auto\"." }, { "name": "connection.port_duplex", "test_description": "The network switch port connected to the device has auto-negotiated full-duplex", - "expected_behavior": "When the ethernet cable is connected to the port, the device autonegotiates a full-duplex connection.", - "required_result": "Required" + "expected_behavior": "When the ethernet cable is connected to the port, the device autonegotiates a full-duplex connection." }, { "name": "connection.switch.arp_inspection", "test_description": "The device implements ARP correctly as per RFC826", - "expected_behavior": "Device continues to operate correctly when ARP inspection is enabled on the switch. No functionality is lost with ARP inspection enabled.", - "required_result": "Required" + "expected_behavior": "Device continues to operate correctly when ARP inspection is enabled on the switch. No functionality is lost with ARP inspection enabled." }, { "name": "connection.switch.dhcp_snooping", "test_description": "The device operates as a DHCP client and operates correctly when DHCP snooping is enabled on a switch.", - "expected_behavior": "Device continues to operate correctly when DHCP snooping is enabled on the switch.", - "required_result": "Required" + "expected_behavior": "Device continues to operate correctly when DHCP snooping is enabled on the switch." }, { "name": "connection.dhcp_address", "test_description": "The device under test has received an IP address from the DHCP server and responds to an ICMP echo (ping) request", "expected_behavior": "The device is not setup with a static IP address. The device accepts an IP address from a DHCP server (RFC 2131) and responds succesfully to an ICMP echo (ping) request.", - "required_result": "Required", "recommendations": [ "Enable DHCP", "Install a DHCP client" @@ -57,7 +51,6 @@ "name": "connection.mac_address", "test_description": "Check and note device physical address.", "expected_behavior": "N/A", - "required_result": "Required", "recommendations": [ "Ensure that the MAC address is set by hardware only" ] @@ -66,7 +59,6 @@ "name": "connection.mac_oui", "test_description": "The device under test has a MAC address prefix that is registered against a known manufacturer.", "expected_behavior": "The MAC address prefix is registered in the IEEE Organizationally Unique Identifier database.", - "required_result": "Required", "recommendations": [ "Register the device MAC address with IEEE" ] @@ -75,7 +67,6 @@ "name": "connection.private_address", "test_description": "The device under test accepts an IP address that is compliant with RFC 1918 Address Allocation for Private Internets.", "expected_behavior": "The device under test accepts IP addresses within all ranges specified in RFC 1918 and communicates using these addresses. The Internet Assigned Numbers Authority (IANA) has reserved the following three blocks of the IP address space for private internets. 10.0.0.0 - 10.255.255.255.255 (10/8 prefix). 172.16.0.0 - 172.31.255.255 (172.16/12 prefix). 192.168.0.0 - 192.168.255.255 (192.168/16 prefix)", - "required_result": "Required", "config": { "lease_wait_time_sec": 60, "ranges": [ @@ -101,7 +92,6 @@ "name": "connection.shared_address", "test_description": "Ensure the device supports RFC 6598 IANA-Reserved IPv4 Prefix for Shared Address Space", "expected_behavior": "The device under test accepts IP addresses within the ranges specified in RFC 6598 and communicates using these addresses", - "required_result": "Required", "config": { "lease_wait_time_sec": 60, "ranges": [ @@ -116,11 +106,21 @@ "Enable shared address space support in the DHCP client" ] }, + { + "name": "connection.dhcp_disconnect", + "test_description": "The device under test issues a new DHCPREQUEST packet after a port ph ysical disconnection and reconnection", + "expected_behavior": "A client SHOULD use DHCP to reacquire or verify its IP address and network parameters whenever the local network parameters may have changed; e.g., at system boot time or after a disconnection from the local network, as the local network configuration may change without the client's or user's knowledge. If a client has knowledge ofa previous network address and is unable to contact a local DHCP server, the client may continue to use the previous network addres until the lease for that address expires. If the lease expires before the client can contact a DHCP server, the client must immediately discontinue use of the previous network address and may inform local users of the problem." + }, + { + "name": "connection.dhcp_disconnect_ip_change", + "test_description": "When device is disconnected, update device IP on the DHCP server and reconnect the device. Ensure device received new IP address", + "expected_behavior": "If IP address for a device was changed on the DHCP server while the device was disconnected then the device shoiuld request and update the new IP upon reconnecting to the network", + "required_result": "Required" + }, { "name": "connection.single_ip", "test_description": "The network switch port connected to the device reports only one IP address for the device under test.", "expected_behavior": "The device under test does not behave as a network switch and only requets one IP address. This test is to avoid that devices implement network switches that allow connecting strings of daisy chained devices to one single network port, as this would not make 802.1x port based authentication possible.", - "required_result": "Required", "recommendations": [ "Ensure that all ports on the device are isolated", "Ensure only one DHCP client is running" @@ -130,7 +130,6 @@ "name": "connection.target_ping", "test_description": "The device under test responds to an ICMP echo (ping) request.", "expected_behavior": "The device under test responds to an ICMP echo (ping) request.", - "required_result": "Required", "recommendations": [ "Configure device to allow ICMP requests (ping)", "Create a firewall exception to allow ICMP requests from LAN" @@ -140,7 +139,6 @@ "name": "connection.ipaddr.ip_change", "test_description": "The device responds to a ping (ICMP echo request) to the new IP address it has received after the initial DHCP lease has expired.", "expected_behavior": "If the lease expires before the client receiveds a DHCPACK, the client moves to INIT state, MUST immediately stop any other network processing and requires network initialization parameters as if the client were uninitialized. If the client then receives a DHCPACK allocating the client its previous network addres, the client SHOULD continue network processing. If the client is given a new network address, it MUST NOT continue using the previous network address and SHOULD notify the local users of the problem.", - "required_result": "Required", "config":{ "lease_wait_time_sec": 60 }, @@ -152,7 +150,6 @@ "name": "connection.ipaddr.dhcp_failover", "test_description": "The device has requested a DHCPREQUEST/REBIND to the DHCP failover server after the primary DHCP server has been brought down.", "expected_behavior": "", - "required_result": "Required", "config":{ "lease_wait_time_sec": 60 }, @@ -164,7 +161,6 @@ "name": "connection.ipv6_slaac", "test_description": "The device forms a valid IPv6 address as a combination of the IPv6 router prefix and the device interface identifier", "expected_behavior": "The device under test complies with RFC4862 and forms a valid IPv6 SLAAC address", - "required_result": "Required", "recommendations": [ "Install a network manager that supports IPv6", "Disable DHCPv6" @@ -174,7 +170,6 @@ "name": "connection.ipv6_ping", "test_description": "The device responds to an IPv6 ping (ICMPv6 Echo) request to the SLAAC address", "expected_behavior": "The device responds to the ping as per RFC4443", - "required_result": "Required", "recommendations": [ "Enable ping response to IPv6 ICMP requests in network manager settings", "Create a firewall exception to allow ICMPv6 via LAN" diff --git a/modules/test/conn/conn.Dockerfile b/modules/test/conn/conn.Dockerfile index a9f523e44..58aec8048 100644 --- a/modules/test/conn/conn.Dockerfile +++ b/modules/test/conn/conn.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/conn-test -FROM test-run/base-test:latest +# Image name: testrun/conn-test +FROM testrun/base-test:latest ARG MODULE_NAME=conn ARG MODULE_DIR=modules/test/$MODULE_NAME @@ -27,7 +27,7 @@ RUN apt-get install -y wget COPY $MODULE_DIR/python/requirements.txt /testrun/python # Install all python requirements for the module -RUN pip3 install -r /testrun/python/requirements.txt +RUN pip install -r /testrun/python/requirements.txt # Copy over all configuration files COPY $MODULE_DIR/conf /testrun/conf diff --git a/modules/test/conn/python/src/connection_module.py b/modules/test/conn/python/src/connection_module.py index 88dd40393..867d8a3ff 100644 --- a/modules/test/conn/python/src/connection_module.py +++ b/modules/test/conn/python/src/connection_module.py @@ -15,10 +15,12 @@ import util import time import traceback +import os from scapy.all import rdpcap, DHCP, ARP, Ether, ICMP, IPv6, ICMPv6ND_NS from test_module import TestModule from dhcp1.client import Client as DHCPClient1 from dhcp2.client import Client as DHCPClient2 +from host.client import Client as HostClient from dhcp_util import DHCPUtil from port_stats_util import PortStatsUtil @@ -59,6 +61,7 @@ def __init__(self, self._port_stats = PortStatsUtil(logger=LOGGER) self.dhcp1_client = DHCPClient1() self.dhcp2_client = DHCPClient2() + self.host_client = HostClient() self._dhcp_util = DHCPUtil(self.dhcp1_client, self.dhcp2_client, LOGGER) self._lease_wait_time_sec = LEASE_WAIT_TIME_DEFAULT @@ -243,7 +246,8 @@ def _connection_single_ip(self): if self._get_dhcp_type(packet) == 3: mac_address = packet[Ether].src LOGGER.info('DHCPREQUEST detected MAC address: ' + mac_address) - if not mac_address.startswith(TR_CONTAINER_MAC_PREFIX): + if (not mac_address.startswith(TR_CONTAINER_MAC_PREFIX) + and mac_address != self._dev_iface_mac): mac_addresses.add(mac_address.upper()) # Check if the device mac address is in the list of DHCPREQUESTs @@ -379,6 +383,171 @@ def _connection_ipaddr_dhcp_failover(self, config): result = None, 'Network is not ready for this test' return result + def _connection_dhcp_disconnect(self): + LOGGER.info('Running connection.dhcp.disconnect') + result = None + description = '' + dev_iface = os.getenv('DEV_IFACE') + iface_status = self.host_client.check_interface_status(dev_iface) + if iface_status.code == 200: + LOGGER.info('Successfully resolved iface status') + if iface_status.status: + lease = self._dhcp_util.get_cur_lease(mac_address=self._device_mac, + timeout=self._lease_wait_time_sec) + if lease is not None: + LOGGER.info('Current device lease resolved') + if self._dhcp_util.is_lease_active(lease): + + # Disable the device interface + iface_down = self.host_client.set_iface_down(dev_iface) + if iface_down: + LOGGER.info('Device interface set to down state') + + # Wait for the lease to expire + self._dhcp_util.wait_for_lease_expire(lease, + self._lease_wait_time_sec) + + # Wait an additonal 10 seconds to better test a true disconnect + # state + LOGGER.info('Waiting 10 seconds before bringing iface back up') + time.sleep(10) + + # Enable the device interface + iface_up = self.host_client.set_iface_up(dev_iface) + if iface_up: + LOGGER.info('Device interface set to up state') + + # Confirm device receives a new lease + if self._dhcp_util.get_cur_lease( + mac_address=self._device_mac, + timeout=self._lease_wait_time_sec): + if self._dhcp_util.is_lease_active(lease): + result = True + description = ( + 'Device received a DHCP lease after disconnect') + else: + result = False + description = ( + 'Could not confirm DHCP lease active after disconnect') + else: + result = False + description = ( + 'Device did not recieve a DHCP lease after disconnect') + else: + result = 'Error' + description = 'Failed to set device interface to up state' + else: + result = 'Error' + description = 'Failed to set device interface to down state' + else: + result = 'Error' + description = 'No active lease available for device' + else: + result = 'Error' + description = 'Device interface is down' + else: + result = 'Error' + description = 'Device interface could not be resolved' + return result, description + + def _connection_dhcp_disconnect_ip_change(self): + LOGGER.info('Running connection.dhcp.disconnect_ip_change') + result = None + description = '' + reserved_lease = None + dev_iface = os.getenv('DEV_IFACE') + if self._dhcp_util.setup_single_dhcp_server(): + iface_status = self.host_client.check_interface_status(dev_iface) + if iface_status.code == 200: + LOGGER.info('Successfully resolved iface status') + if iface_status.status: + lease = self._dhcp_util.get_cur_lease( + mac_address=self._device_mac, timeout=self._lease_wait_time_sec) + if lease is not None: + LOGGER.info('Current device lease resolved') + if self._dhcp_util.is_lease_active(lease): + + # Add a reserved lease with a different IP + ip_address = '10.10.10.30' + reserved_lease = self._dhcp_util.add_reserved_lease( + lease['hostname'], self._device_mac, ip_address) + + # Disable the device interface + iface_down = self.host_client.set_iface_down(dev_iface) + if iface_down: + LOGGER.info('Device interface set to down state') + + # Wait for the lease to expire + self._dhcp_util.wait_for_lease_expire(lease, + self._lease_wait_time_sec) + + if reserved_lease: + # Wait an additonal 10 seconds to better test a true + # disconnect state + LOGGER.info( + 'Waiting 10 seconds before bringing iface back up') + time.sleep(10) + + # Enable the device interface + iface_up = self.host_client.set_iface_up(dev_iface) + if iface_up: + LOGGER.info('Device interface set to up state') + # Confirm device receives a new lease + reserved_lease_accepted = False + LOGGER.info('Checking device accepted new ip') + for _ in range(5): + LOGGER.info('Pinging device at IP: ' + ip_address) + if self._ping(ip_address): + LOGGER.debug('Ping success') + LOGGER.debug( + 'Reserved lease confirmed active in device') + reserved_lease_accepted = True + break + else: + LOGGER.info('Device did not respond to ping') + time.sleep(5) # Wait 5 seconds before trying again + + if reserved_lease_accepted: + result = True + description = ('Device received expected IP address ' + 'after disconnect') + else: + result = False + description = ( + 'Could not confirm DHCP lease active after disconnect' + ) + else: + result = 'Error' + description = 'Failed to set device interface to up state' + else: + result = 'Error' + description = 'Failed to set reserved address in DHCP server' + else: + result = 'Error' + description = 'Failed to set device interface to down state' + else: + result = 'Error' + description = 'No active lease available for device' + else: + result = 'Error' + description = 'Device interface is down' + else: + result = 'Error' + description = 'Device interface could not be resolved' + else: + result = 'Error' + description = 'Failed to configure network for test' + if reserved_lease: + self._dhcp_util.delete_reserved_lease(self._device_mac) + + # Restore the network + self._dhcp_util.restore_failover_dhcp_server() + LOGGER.info('Waiting 30 seconds for reserved lease to expire') + time.sleep(30) + self._dhcp_util.get_cur_lease(mac_address=self._device_mac, + timeout=self._lease_wait_time_sec) + return result, description + def _get_oui_manufacturer(self, mac_address): # Do some quick fixes on the format of the mac_address # to match the oui file pattern diff --git a/modules/test/dns/conf/module_config.json b/modules/test/dns/conf/module_config.json index f048d5deb..d3d0a1134 100644 --- a/modules/test/dns/conf/module_config.json +++ b/modules/test/dns/conf/module_config.json @@ -16,7 +16,6 @@ "name": "dns.network.hostname_resolution", "test_description": "Verify the device sends DNS requests", "expected_behavior": "The device sends DNS requests.", - "required_result": "Required", "recommendations": [ "Install a supported DNS client", "Ensure DNS servers have been set correctly", @@ -27,7 +26,6 @@ "name": "dns.network.from_dhcp", "test_description": "Verify the device allows for a DNS server to be entered automatically", "expected_behavior": "The device sends DNS requests to the DNS server provided by the DHCP server", - "required_result": "Informational", "recommendations": [ "Install a DNS client that supports fetching DNS servers from DHCP options" ] @@ -35,8 +33,7 @@ { "name": "dns.mdns", "test_description": "Does the device has MDNS (or any kind of IP multicast)", - "expected_behavior": "Device may send MDNS requests", - "required_result": "Informational" + "expected_behavior": "Device may send MDNS requests" } ] } diff --git a/modules/test/dns/dns.Dockerfile b/modules/test/dns/dns.Dockerfile index 0197fd72e..461e87899 100644 --- a/modules/test/dns/dns.Dockerfile +++ b/modules/test/dns/dns.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/conn-test -FROM test-run/base-test:latest +# Image name: testrun/conn-test +FROM testrun/base-test:latest ARG MODULE_NAME=dns ARG MODULE_DIR=modules/test/$MODULE_NAME @@ -22,7 +22,7 @@ ARG MODULE_DIR=modules/test/$MODULE_NAME COPY $MODULE_DIR/python/requirements.txt /testrun/python # Install all python requirements for the module -RUN pip3 install -r /testrun/python/requirements.txt +RUN pip install -r /testrun/python/requirements.txt # Copy over all configuration files COPY $MODULE_DIR/conf /testrun/conf diff --git a/modules/test/dns/python/src/dns_module.py b/modules/test/dns/python/src/dns_module.py index c04e289d3..2d98c36dc 100644 --- a/modules/test/dns/python/src/dns_module.py +++ b/modules/test/dns/python/src/dns_module.py @@ -53,7 +53,7 @@ def generate_module_report(self): # Extract DNS data from the pcap file dns_table_data = self.extract_dns_data() - html_content = '

DNS Module

' + html_content = '

DNS Module

' # Set the summary variables local_requests = sum( @@ -97,6 +97,7 @@ def generate_module_report(self): Source Destination + Resolved IP Type URL Count @@ -105,16 +106,16 @@ def generate_module_report(self): ''' # Count unique combinations - counter = Counter( - (row['Source'], row['Destination'], row['Type'], row['Data']) - for row in dns_table_data) + counter = Counter((row['Source'], row['Destination'], row['ResolvedIP'], + row['Type'], row['Data']) for row in dns_table_data) # Generate the HTML table with the count column - for (src, dst, typ, dat), count in counter.items(): + for (src, dst, res_ip, typ, dat), count in counter.items(): table_content += f''' {src} {dst} + {res_ip} {typ} {dat} {count} @@ -122,8 +123,7 @@ def generate_module_report(self): table_content += ''' - - ''' + ''' html_content += table_content @@ -166,23 +166,38 @@ def extract_dns_data(self): # 'qr' field indicates query (0) or response (1) dns_type = 'Query' if dns_layer.qr == 0 else 'Response' - # Check for the presence of DNS query name - if hasattr(dns_layer, 'qd') and dns_layer.qd is not None: + # Check if 'qd' (query data) exists and has at least one entry + if hasattr(dns_layer, 'qd') and dns_layer.qdcount > 0: qname = dns_layer.qd.qname.decode() if dns_layer.qd.qname else 'N/A' else: qname = 'N/A' + resolved_ip = 'N/A' + # If it's a response packet, extract the resolved IP address + # from the answer section + if dns_layer.qr == 1 and hasattr(dns_layer, + 'an') and dns_layer.ancount > 0: + # Loop through all answers in the DNS response + for i in range(dns_layer.ancount): + answer = dns_layer.an[i] + # Check for IPv4 (A record) or IPv6 (AAAA record) + if answer.type == 1: # Indicates an A record (IPv4 address) + resolved_ip = answer.rdata # Extract IPv4 address + break # Stop after finding the first valid resolved IP + elif answer.type == 28: # Indicates an AAAA record (IPv6 address) + resolved_ip = answer.rdata # Extract IPv6 address + break # Stop after finding the first valid resolved IP + dns_data.append({ 'Timestamp': float(packet.time), # Timestamp of the DNS packet 'Source': source_ip, 'Destination': destination_ip, + 'ResolvedIP': resolved_ip, # Adding the resolved IP address 'Type': dns_type, 'Data': qname[:-1] }) # Filter unique entries based on 'Timestamp' - # DNS Server will duplicate messages caught by - # startup and monitor filtered_unique_dns_data = [] seen_timestamps = set() diff --git a/modules/test/ntp/conf/module_config.json b/modules/test/ntp/conf/module_config.json index 55eb3df76..6634b127d 100644 --- a/modules/test/ntp/conf/module_config.json +++ b/modules/test/ntp/conf/module_config.json @@ -9,14 +9,13 @@ "docker": { "depends_on": "base", "enable_container": true, - "timeout": 30 + "timeout": 60 }, "tests":[ { "name": "ntp.network.ntp_support", "test_description": "Does the device request network time sync as client as per RFC 5905 - Network Time Protocol Version 4: Protocol and Algorithms Specification", "expected_behavior": "The device sends an NTPv4 request to the configured NTP server.", - "required_result": "Required", "recommendations": [ "Set the NTP version to v4 in the NTP client", "Install an NTP client that supports NTPv4" @@ -26,7 +25,6 @@ "name": "ntp.network.ntp_dhcp", "test_description": "Accept NTP address over DHCP", "expected_behavior": "Device can accept NTP server address, provided by the DHCP server (DHCP OFFER PACKET)", - "required_result": "Roadmap", "recommendations": [ "Install an NTP client that supports fetching the NTP servers from DHCP options" ] diff --git a/modules/test/ntp/ntp.Dockerfile b/modules/test/ntp/ntp.Dockerfile index 33b06287e..4d9701464 100644 --- a/modules/test/ntp/ntp.Dockerfile +++ b/modules/test/ntp/ntp.Dockerfile @@ -1,5 +1,19 @@ -# Image name: test-run/ntp-test -FROM test-run/base-test:latest +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Image name: testrun/ntp-test +FROM testrun/base-test:latest ARG MODULE_NAME=ntp ARG MODULE_DIR=modules/test/$MODULE_NAME @@ -8,7 +22,7 @@ ARG MODULE_DIR=modules/test/$MODULE_NAME COPY $MODULE_DIR/python/requirements.txt /testrun/python # Install all python requirements for the module -RUN pip3 install -r /testrun/python/requirements.txt +RUN pip install -r /testrun/python/requirements.txt # Copy over all configuration files COPY $MODULE_DIR/conf /testrun/conf diff --git a/modules/test/ntp/python/src/ntp_module.py b/modules/test/ntp/python/src/ntp_module.py index be27abbad..f82240ff1 100644 --- a/modules/test/ntp/python/src/ntp_module.py +++ b/modules/test/ntp/python/src/ntp_module.py @@ -54,7 +54,7 @@ def generate_module_report(self): # Extract NTP data from the pcap file ntp_table_data = self.extract_ntp_data() - html_content = '

NTP Module

' + html_content = '

NTP Module

' # Set the summary variables local_requests = sum( @@ -267,11 +267,9 @@ def _ntp_network_ntp_support(self): result = False, 'Device has not sent any NTP requests' if device_sends_ntp3 and device_sends_ntp4: - result = False, ('Device sent NTPv3 and NTPv4 packets. ' + - 'NTPv3 is not allowed') + result = False, ('Device sent NTPv3 and NTPv4 packets') elif device_sends_ntp3: - result = False, ('Device sent NTPv3 packets. ' - 'NTPv3 is not allowed') + result = False, ('Device sent NTPv3 packets') elif device_sends_ntp4: result = True, 'Device sent NTPv4 packets' diff --git a/modules/test/protocol/conf/module_config.json b/modules/test/protocol/conf/module_config.json index 365bd346b..554f43cc7 100644 --- a/modules/test/protocol/conf/module_config.json +++ b/modules/test/protocol/conf/module_config.json @@ -16,20 +16,17 @@ { "name": "protocol.valid_bacnet", "test_description": "Can valid BACnet traffic be seen", - "expected_behavior": "BACnet traffic can be seen on the network and packets are valid and not malformed", - "required_result": "Recommended" + "expected_behavior": "BACnet traffic can be seen on the network and packets are valid and not malformed" }, { "name": "protocol.bacnet.version", "test_description": "Obtain the version of BACnet client used", - "expected_behavior": "The BACnet client implements an up to date version of BACnet", - "required_result": "Recommended" + "expected_behavior": "The BACnet client implements an up to date version of BACnet" }, { "name": "protocol.valid_modbus", "test_description": "Can valid Modbus traffic be seen", "expected_behavior": "Any Modbus functionality works as expected and valid Modbus traffic can be observed", - "required_result": "Recommended", "config":{ "port": 502, "device_id": 1, diff --git a/modules/test/protocol/protocol.Dockerfile b/modules/test/protocol/protocol.Dockerfile index 6f55520e1..4494ae94e 100644 --- a/modules/test/protocol/protocol.Dockerfile +++ b/modules/test/protocol/protocol.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/protocol-test -FROM test-run/base-test:latest +# Image name: testrun/protocol-test +FROM testrun/base-test:latest # Set DEBIAN_FRONTEND to noninteractive mode ENV DEBIAN_FRONTEND=noninteractive @@ -28,7 +28,7 @@ ARG MODULE_DIR=modules/test/$MODULE_NAME COPY $MODULE_DIR/python/requirements.txt /testrun/python #Install all python requirements for the module -RUN pip3 install -r /testrun/python/requirements.txt +RUN pip install -r /testrun/python/requirements.txt # Copy over all configuration files COPY $MODULE_DIR/conf /testrun/conf diff --git a/modules/test/services/conf/module_config.json b/modules/test/services/conf/module_config.json index 5c20b4beb..fecb41862 100644 --- a/modules/test/services/conf/module_config.json +++ b/modules/test/services/conf/module_config.json @@ -16,7 +16,6 @@ "name": "security.services.ftp", "test_description": "Check FTP port 20/21 is disabled and FTP is not running on any port", "expected_behavior": "There is no FTP service running on any port", - "required_result": "Required", "config": { "services": [ "ftp", @@ -50,7 +49,6 @@ "name": "security.ssh.version", "test_description": "If the device is running a SSH server ensure it is SSHv2", "expected_behavior": "SSH server is not running or server is SSHv2", - "required_result": "Required", "config": { "services": ["ssh"], "ports": [ @@ -70,7 +68,6 @@ "name": "security.services.telnet", "test_description": "Check TELNET port 23 is disabled and TELNET is not running on any port", "expected_behavior": "There is no Telnet service running on any port", - "required_result": "Required", "config": { "services": [ "telnet" @@ -95,7 +92,6 @@ "name": "security.services.smtp", "test_description": "Check SMTP ports 25, 465 and 587 are not enabled and SMTP is not running on any port.", "expected_behavior": "There is no SMTP service running on any port", - "required_result": "Required", "config": { "services": [ "smtp" @@ -123,7 +119,6 @@ "name": "security.services.http", "test_description": "Check that there is no HTTP server running on any port", "expected_behavior": "Device is unreachable on port 80 (or any other port) and only responds to HTTPS requests on port 443 (or any other port if HTTP is used at all)", - "required_result": "Required", "config": { "services": [ "http" @@ -158,7 +153,6 @@ "name": "security.services.pop", "test_description": "Check POP ports 109 and 110 are disabled and POP is not running on any port", "expected_behavior": "There is no POP service running on any port", - "required_result": "Required", "config": { "services": [ "pop2", @@ -200,7 +194,6 @@ "name": "security.services.imap", "test_description": "Check IMAP port 143 is disabled and IMAP is not running on any port", "expected_behavior": "There is no IMAP service running on any port", - "required_result": "Required", "config": { "services": [ "imap", @@ -250,7 +243,6 @@ "name": "security.services.snmpv3", "test_description": "Check SNMP port 161/162 is disabled. If SNMP is an essential service, check it supports version 3", "expected_behavior": "Device is unreachable on port 161 (or any other port) and device is unreachable on port 162 (or any other port) unless SNMP is essential in which case it is SNMPv3 is used.", - "required_result": "Required", "config": { "services": [ "snmp" @@ -275,7 +267,6 @@ "name": "security.services.vnc", "test_description": "Check VNC is disabled on any port", "expected_behavior": "Device cannot be accessed / connected to via VNC on any port", - "required_result": "Required", "config": { "services": [ "vnc", @@ -330,7 +321,6 @@ "name": "security.services.tftp", "test_description": "Check TFTP port 69 is disabled (UDP)", "expected_behavior": "There is no TFTP service running on any port", - "required_result": "Required", "config": { "services": [ "tftp", @@ -363,7 +353,6 @@ "name": "ntp.network.ntp_server", "test_description": "Check NTP port 123 is disabled and the device is not operating as an NTP server", "expected_behavior": "The device does not respond to NTP requests when it's IP is set as the NTP server on another device", - "required_result": "Required", "config": { "services": [ "ntp" diff --git a/modules/test/services/python/src/services_module.py b/modules/test/services/python/src/services_module.py index b14c74234..a96d47bc0 100644 --- a/modules/test/services/python/src/services_module.py +++ b/modules/test/services/python/src/services_module.py @@ -83,7 +83,7 @@ def generate_module_report(self): else: udp_open += 1 - html_content = '

Services Module

' + html_content = '

Services Module

' # Add summary table html_content += (f''' diff --git a/modules/test/services/services.Dockerfile b/modules/test/services/services.Dockerfile index 3a89fc33c..8dcaafcc1 100644 --- a/modules/test/services/services.Dockerfile +++ b/modules/test/services/services.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/services-test -FROM test-run/base-test:latest +# Image name: testrun/services-test +FROM testrun/base-test:latest ARG MODULE_NAME=services ARG MODULE_DIR=modules/test/$MODULE_NAME @@ -22,7 +22,7 @@ ARG MODULE_DIR=modules/test/$MODULE_NAME COPY $MODULE_DIR/python/requirements.txt /testrun/python # Install all python requirements for the module -RUN pip3 install -r /testrun/python/requirements.txt +RUN pip install -r /testrun/python/requirements.txt # Copy over all configuration files COPY $MODULE_DIR/conf /testrun/conf diff --git a/modules/test/tls/bin/get_client_hello_packets.sh b/modules/test/tls/bin/get_client_hello_packets.sh index d563d11f2..317657187 100755 --- a/modules/test/tls/bin/get_client_hello_packets.sh +++ b/modules/test/tls/bin/get_client_hello_packets.sh @@ -21,13 +21,21 @@ TLS_VERSION="$3" TSHARK_OUTPUT="-T json -e ip.src -e tcp.dstport -e ip.dst" TSHARK_FILTER="ssl.handshake.type==1 and ip.src==$SRC_IP" -if [[ $TLS_VERSION == '1.2' || -z $TLS_VERSION ]];then +if [[ $TLS_VERSION == '1.0' ]]; then + TSHARK_FILTER="$TSHARK_FILTER and ssl.handshake.version==0x0301" +elif [[ $TLS_VERSION == '1.1' ]]; then + TSHARK_FILTER="$TSHARK_FILTER and ssl.handshake.version==0x0302" +elif [[ $TLS_VERSION == '1.2' || -z $TLS_VERSION ]]; then TSHARK_FILTER="$TSHARK_FILTER and ssl.handshake.version==0x0303" -elif [ $TLS_VERSION == '1.3' ];then +elif [[ $TLS_VERSION == '1.3' ]]; then TSHARK_FILTER="$TSHARK_FILTER and (ssl.handshake.version==0x0304 or tls.handshake.extensions.supported_version==0x0304)" +else + echo "Unsupported TLS version: $TLS_VERSION" + exit 1 fi response=$(tshark -r "$CAPTURE_FILE" $TSHARK_OUTPUT $TSHARK_FILTER) echo "$response" + \ No newline at end of file diff --git a/modules/test/tls/bin/get_handshake_complete.sh b/modules/test/tls/bin/get_handshake_complete.sh index 9bf9c525d..b36997f6d 100755 --- a/modules/test/tls/bin/get_handshake_complete.sh +++ b/modules/test/tls/bin/get_handshake_complete.sh @@ -1,33 +1,39 @@ -#!/bin/bash - -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -CAPTURE_FILE="$1" -SRC_IP="$2" -DST_IP="$3" -TLS_VERSION="$4" - -TSHARK_FILTER="ip.src==$SRC_IP and ip.dst==$DST_IP " - -if [[ $TLS_VERSION == '1.2' || -z $TLS_VERSION ]];then - TSHARK_FILTER=$TSHARK_FILTER " and ssl.handshake.type==2 and tls.handshake.type==14 " -elif [ $TLS_VERSION == '1.2' ];then - TSHARK_FILTER=$TSHARK_FILTER "and ssl.handshake.type==2 and tls.handshake.extensions.supported_version==0x0304" -fi - -response=$(tshark -r "$CAPTURE_FILE" $TSHARK_FILTER) - -echo "$response" - \ No newline at end of file +#!/bin/bash + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +CAPTURE_FILE="$1" +SRC_IP="$2" +DST_IP="$3" +TLS_VERSION="$4" + +TSHARK_FILTER="ip.src==$SRC_IP and ip.dst==$DST_IP" + +if [[ $TLS_VERSION == '1.0' ]]; then + TSHARK_FILTER=$TSHARK_FILTER "and ssl.handshake.type==2 and tls.handshake.type==14" +elif [[ $TLS_VERSION == '1.1' ]]; then + TSHARK_FILTER=$TSHARK_FILTER "and ssl.handshake.type==2 and tls.handshake.type==14" +elif [[ $TLS_VERSION == '1.2' || -z $TLS_VERSION ]]; then + TSHARK_FILTER=$TSHARK_FILTER "and ssl.handshake.type==2 and tls.handshake.type==14" +elif [[ $TLS_VERSION == '1.3' ]]; then + TSHARK_FILTER=$TSHARK_FILTER "and ssl.handshake.type==2 and tls.handshake.extensions.supported_version==0x0304" +else + echo "Unsupported TLS version: $TLS_VERSION" + exit 1 +fi + +response=$(tshark -r "$CAPTURE_FILE" $TSHARK_FILTER) + +echo "$response" diff --git a/modules/test/tls/conf/module_config.json b/modules/test/tls/conf/module_config.json index c74bfd667..7058129f2 100644 --- a/modules/test/tls/conf/module_config.json +++ b/modules/test/tls/conf/module_config.json @@ -12,11 +12,19 @@ "timeout": 300 }, "tests":[ + { + "name": "security.tls.v1_0_client", + "test_description": "Device uses TLS with connection to an external service on port 443 (or any other port which could be running the webserver-HTTPS)", + "expected_behavior": "The packet indicates a TLS connection with at least TLS 1.0 and support", + "recommendations": [ + "Disable connections to unsecure services", + "Ensure any URLs connected to are secure (https)" + ] + }, { "name": "security.tls.v1_2_server", "test_description": "Check the device web server TLS 1.2 & certificate is valid", "expected_behavior": "TLS 1.2 certificate is issued to the web browser client when accessed", - "required_result": "Required if Applicable", "recommendations": [ "Enable TLS 1.2 support in the web server configuration", "Disable TLS 1.0 and 1.1", @@ -27,7 +35,6 @@ "name": "security.tls.v1_2_client", "test_description": "Device uses TLS with connection to an external service on port 443 (or any other port which could be running the webserver-HTTPS)", "expected_behavior": "The packet indicates a TLS connection with at least TLS 1.2 and support for ECDH and ECDSA ciphers", - "required_result": "Required if Applicable", "recommendations": [ "Disable connections to unsecure services", "Ensure any URLs connected to are secure (https)" @@ -37,7 +44,6 @@ "name": "security.tls.v1_3_server", "test_description": "Check the device web server TLS 1.3 & certificate is valid", "expected_behavior": "TLS 1.3 certificate is issued to the web browser client when accessed", - "required_result": "Informational", "recommendations": [ "Enable TLS 1.3 support in the web server configuration", "Disable TLS 1.0 and 1.1", @@ -48,7 +54,25 @@ "name": "security.tls.v1_3_client", "test_description": "Device uses TLS with connection to an external service on port 443 (or any other port which could be running the webserver-HTTPS)", "expected_behavior": "The packet indicates a TLS connection with at least TLS 1.3", - "required_result": "Informational", + "recommendations": [ + "Disable connections to unsecure services", + "Ensure any URLs connected to are secure (https)" + ] + }, + { + "name": "security.tls.v1_3_server", + "test_description": "Check the device web server TLS 1.3 & certificate is valid", + "expected_behavior": "TLS 1.3 certificate is issued to the web browser client when accessed", + "recommendations": [ + "Enable TLS 1.3 support in the web server configuration", + "Disable TLS 1.0 and 1.1", + "Sign the certificate used by the web server" + ] + }, + { + "name": "security.tls.v1_3_client", + "test_description": "Device uses TLS with connection to an external service on port 443 (or any other port which could be running the webserver-HTTPS)", + "expected_behavior": "The packet indicates a TLS connection with at least TLS 1.3", "recommendations": [ "Disable connections to unsecure services", "Ensure any URLs connected to are secure (https)" diff --git a/modules/test/tls/python/requirements.txt b/modules/test/tls/python/requirements.txt index 846a224f3..e03b1f45a 100644 --- a/modules/test/tls/python/requirements.txt +++ b/modules/test/tls/python/requirements.txt @@ -1,5 +1,5 @@ -cryptography==38.0.0 # Do not upgrade until TLS module can be fixed to account for removed x509 property in version 39 -pyOpenSSL==23.0.0 +cryptography==43.0.1 +pyOpenSSL==24.2.1 lxml==5.1.0 # Requirement of pyshark but if upgraded automatically above 5.1 will cause a python crash pyshark==0.6 requests==2.32.3 diff --git a/modules/test/tls/python/src/tls_module.py b/modules/test/tls/python/src/tls_module.py index 9aab1b782..186766b17 100644 --- a/modules/test/tls/python/src/tls_module.py +++ b/modules/test/tls/python/src/tls_module.py @@ -26,6 +26,7 @@ GATEWAY_CAPTURE_FILE = '/runtime/network/gateway.pcap' LOGGER = None + class TLSModule(TestModule): """The TLS testing module.""" @@ -234,8 +235,8 @@ def _security_tls_v1_2_server(self): self._device_ipv4_addr, tls_version='1.2') tls_1_3_results = self._tls_util.validate_tls_server( self._device_ipv4_addr, tls_version='1.3') - results = self._tls_util.process_tls_server_results(tls_1_2_results, - tls_1_3_results) + results = self._tls_util.process_tls_server_results( + tls_1_2_results, tls_1_3_results) # Determine results and return proper messaging and details description = '' if results[0] is None: @@ -244,7 +245,7 @@ def _security_tls_v1_2_server(self): description = 'TLS 1.2 certificate is valid' else: description = 'TLS 1.2 certificate is invalid' - return results[0], description,results[1] + return results[0], description, results[1] else: LOGGER.error('Could not resolve device IP address. Skipping') @@ -256,7 +257,7 @@ def _security_tls_v1_3_server(self): # If the ipv4 address wasn't resolved yet, try again if self._device_ipv4_addr is not None: results = self._tls_util.validate_tls_server(self._device_ipv4_addr, - tls_version='1.3') + tls_version='1.3') # Determine results and return proper messaging and details description = '' if results[0] is None: @@ -265,31 +266,57 @@ def _security_tls_v1_3_server(self): description = 'TLS 1.3 certificate is valid' else: description = 'TLS 1.3 certificate is invalid' - return results[0], description,results[1] + return results[0], description, results[1] else: LOGGER.error('Could not resolve device IP address') return 'Error', 'Could not resolve device IP address' + def _security_tls_v1_0_client(self): + LOGGER.info('Running security.tls.v1_0_client') + self._resolve_device_ip() + # If the ipv4 address wasn't resolved yet, try again + if self._device_ipv4_addr is not None: + tls_1_0_valid = self._validate_tls_client(self._device_ipv4_addr, '1.0') + tls_1_1_valid = self._validate_tls_client(self._device_ipv4_addr, '1.1') + tls_1_2_valid = self._validate_tls_client(self._device_ipv4_addr, '1.2') + tls_1_3_valid = self._validate_tls_client(self._device_ipv4_addr, '1.3') + states = [ + tls_1_0_valid[0], tls_1_1_valid[0], tls_1_2_valid[0], tls_1_3_valid[0] + ] + if any(state is True for state in states): + # If any state is True, return True + result_state = True + result_message = 'TLS 1.0 or higher detected' + elif all(state == 'Feature Not Detected' for state in states): + # If all states are "Feature not Detected" + result_state = 'Feature Not Detected' + result_message = tls_1_0_valid[1] + elif all(state == 'Error' for state in states): + # If all states are "Error" + result_state = 'Error' + result_message = '' + else: + result_state = False + result_message = 'TLS 1.0 or higher was not detected' + result_details = tls_1_0_valid[2] + tls_1_1_valid[2] + tls_1_2_valid[ + 2] + tls_1_3_valid[2] + result_tags = list( + set(tls_1_0_valid[3] + tls_1_1_valid[3] + tls_1_2_valid[3] + + tls_1_3_valid[3])) + return result_state, result_message, result_details, result_tags + else: + LOGGER.error('Could not resolve device IP address. Skipping') + return 'Error', 'Could not resolve device IP address' + def _security_tls_v1_2_client(self): LOGGER.info('Running security.tls.v1_2_client') self._resolve_device_ip() # If the ipv4 address wasn't resolved yet, try again if self._device_ipv4_addr is not None: - results = self._validate_tls_client(self._device_ipv4_addr, '1.2') - # Determine results and return proper messaging and details - description = '' - result = None - if results[0] is None: - description = 'No outbound connections were found' - result = 'Feature Not Detected' - elif results[0]: - description = 'TLS 1.2 client connections valid' - result = True - else: - description = 'TLS 1.2 client connections invalid' - result = False - return result, description, results[1] + return self._validate_tls_client(self._device_ipv4_addr, + '1.2', + unsupported_versions=['1.0', '1.1']) else: LOGGER.error('Could not resolve device IP address. Skipping') return 'Error', 'Could not resolve device IP address' @@ -299,41 +326,43 @@ def _security_tls_v1_3_client(self): self._resolve_device_ip() # If the ipv4 address wasn't resolved yet, try again if self._device_ipv4_addr is not None: - results = self._validate_tls_client(self._device_ipv4_addr, '1.3') - # Determine results and return proper messaging and details - description = '' - if results[0] is None: - description = 'No outbound connections were found' - elif results[0]: - description = 'TLS 1.3 client connections valid' - else: - description = 'TLS 1.3 client connections invalid' - return results[0], description, results[1] + return self._validate_tls_client(self._device_ipv4_addr, + '1.3', + unsupported_versions=['1.0', '1.1']) else: - LOGGER.error('Could not resolve device IP address') + LOGGER.error('Could not resolve device IP address. Skipping') return 'Error', 'Could not resolve device IP address' - def _validate_tls_client(self, client_ip, tls_version): + def _validate_tls_client(self, + client_ip, + tls_version, + unsupported_versions=None): client_results = self._tls_util.validate_tls_client( client_ip=client_ip, tls_version=tls_version, capture_files=[ MONITOR_CAPTURE_FILE, STARTUP_CAPTURE_FILE, TLS_CAPTURE_FILE - ]) + ], + unsupported_versions=unsupported_versions) # Generate results based on the state - result_message = 'No outbound connections were found.' - result_state = 'Feature Not Detected' + result_state = None + result_message = '' + result_details = '' + result_tags = [] - # If any of the packets detect failed client comms, fail the test - if not client_results[0] and client_results[0] is not None: - result_state = False - result_message = client_results[1] - else: + if client_results[0] is not None: + result_details = client_results[1] if client_results[0]: result_state = True - result_message = client_results[1] - return result_state, result_message + result_message = f'TLS {tls_version} client connections valid' + else: + result_state = False + result_message = f'TLS {tls_version} client connections invalid' + else: + result_state = 'Feature Not Detected' + result_message = 'No outbound connections were found' + return result_state, result_message, result_details, result_tags def _resolve_device_ip(self): # If the ipv4 address wasn't resolved yet, try again diff --git a/modules/test/tls/python/src/tls_util.py b/modules/test/tls/python/src/tls_util.py index 0364479c6..9f00b96ef 100644 --- a/modules/test/tls/python/src/tls_util.py +++ b/modules/test/tls/python/src/tls_util.py @@ -236,8 +236,8 @@ def validate_cert_chain(self, device_cert_path): ca_issuer_cert, cert_file_path = self.get_ca_issuer(certificate) if ca_issuer_cert is not None and cert_file_path is not None: LOGGER.info('CA Issuer resolved') - cert_text = crypto.dump_certificate(crypto.FILETYPE_TEXT, - ca_issuer_cert).decode() + cert_text = ca_issuer_cert.public_bytes( + encoding=serialization.Encoding.PEM).decode() LOGGER.info(cert_text) return self.validate_trusted_ca_signature_chain( device_cert_path=device_cert_path, @@ -551,7 +551,7 @@ def process_hello_packets(self, f'\nAllowing {protocol_name} traffic to {packet["dst_ip"]}') client_hello_results['valid'].append(packet) else: - # No cipher check for TLS 1.3 + # No cipher check for TLS 1.0, 1.1 or TLS 1.3 client_hello_results['valid'] = hello_packets return client_hello_results @@ -589,36 +589,31 @@ def get_non_tls_client_connection_ips(self, client_ip, capture_files): # we will assume any local connections using the same IP subnet as our # local network are approved and only connections to IP addresses outside # our network will be flagged. - def get_unsupported_tls_ips(self, client_ip, capture_files): + def get_unsupported_tls_ips(self, + client_ip, + capture_files, + unsupported_versions=None): LOGGER.info('Checking client for unsupported TLS client connections') - tls_1_0_packets = self.get_tls_packets(capture_files, client_ip, '1.0') - tls_1_1_packets = self.get_tls_packets(capture_files, client_ip, '1.1') - unsupported_tls_dst_ips = {} - if len(tls_1_0_packets) > 0: - for packet in tls_1_0_packets: - dst_ip = packet['dst_ip'] - tls_version = '1.0' - if dst_ip not in unsupported_tls_dst_ips: - LOGGER.info(f'''Unsupported TLS {tls_version} - connections detected to {dst_ip}''') - unsupported_tls_dst_ips[dst_ip] = [tls_version] - - if len(tls_1_1_packets) > 0: - for packet in tls_1_1_packets: - dst_ip = packet['dst_ip'] - tls_version = '1.1' - # Check if the IP is already in the dictionary - if dst_ip in unsupported_tls_dst_ips: - # If the IP is already present, append the new TLS version to the - # list - unsupported_tls_dst_ips[dst_ip].append(tls_version) - else: - # If the IP is not present, create a new list with the current - # TLS version - LOGGER.info(f'''Unsupported TLS {tls_version} connections detected - to {dst_ip}''') - unsupported_tls_dst_ips[dst_ip] = [tls_version] + if unsupported_versions is not None: + for unsupported_version in unsupported_versions: + tls_packets = self.get_tls_packets(capture_files, client_ip, '1.0') + if len(tls_packets) > 0: + for packet in tls_packets: + dst_ip = packet['dst_ip'] + tls_version = unsupported_version + if dst_ip not in unsupported_tls_dst_ips: + # If the IP is already present, append the new TLS version to the + # list + LOGGER.info(f'''Unsupported TLS {tls_version} + connections detected to {dst_ip}''') + unsupported_tls_dst_ips[dst_ip] = [tls_version] + else: + # If the IP is not present, create a new list with the current + # TLS version + LOGGER.info(f'''Unsupported TLS {tls_version} connections detected + to {dst_ip}''') + unsupported_tls_dst_ips[dst_ip] = [tls_version] return unsupported_tls_dst_ips # Check if the device has made any outbound connections that use any @@ -657,20 +652,26 @@ def is_private_ip(self, ip): return True return False - def validate_tls_client(self, client_ip, tls_version, capture_files): + def validate_tls_client(self, + client_ip, + tls_version, + capture_files, + unsupported_versions=None): LOGGER.info('Validating client for TLS: ' + tls_version) hello_packets = self.get_hello_packets(capture_files, client_ip, tls_version) # Resolve allowed protocol connections that require # additional consideration beyond packet inspection - allowed_protocol_client_ips = ( - self.get_allowed_protocol_client_connection_ips(client_ip, - capture_files)) + protocol_client_ips = (self.get_allowed_protocol_client_connection_ips( + client_ip, capture_files)) - LOGGER.info(f'Protocol IPS: {allowed_protocol_client_ips}') - client_hello_results = self.process_hello_packets( - hello_packets, allowed_protocol_client_ips, tls_version) + if len(protocol_client_ips) > 0: + LOGGER.info( + f'Allowed Protocol IP connections detected: {protocol_client_ips}') + client_hello_results = self.process_hello_packets(hello_packets, + protocol_client_ips, + tls_version) handshakes = {'complete': [], 'incomplete': []} for packet in client_hello_results['valid']: @@ -758,7 +759,8 @@ def validate_tls_client(self, client_ip, tls_version, capture_files): LOGGER.info(f'''TLS connection detected to {ip}. Ignoring non-TLS traffic detected to this IP''') - unsupported_tls_ips = self.get_unsupported_tls_ips(client_ip, capture_files) + unsupported_tls_ips = self.get_unsupported_tls_ips(client_ip, capture_files, + unsupported_versions) if len(unsupported_tls_ips) > 0: tls_client_valid = False for ip, tls_versions in unsupported_tls_ips.items(): diff --git a/modules/test/tls/tls.Dockerfile b/modules/test/tls/tls.Dockerfile index 987ede591..c448c8478 100644 --- a/modules/test/tls/tls.Dockerfile +++ b/modules/test/tls/tls.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/tls-test -FROM test-run/base-test:latest +# Image name: testrun/tls-test +FROM testrun/base-test:latest # Set DEBIAN_FRONTEND to noninteractive mode ENV DEBIAN_FRONTEND=noninteractive @@ -41,7 +41,10 @@ RUN chmod u+x /testrun/bin/* COPY $MODULE_DIR/python /testrun/python # Install all python requirements for the module -RUN pip3 install -r /testrun/python/requirements.txt +RUN pip install -r /testrun/python/requirements.txt + +# Install all python requirements for the modules unit test +RUN pip install -r /testrun/python/requirements-test.txt # Install all python requirements for the modules unit test RUN pip3 install -r /testrun/python/requirements-test.txt diff --git a/modules/ui/angular.json b/modules/ui/angular.json index 0bf42377f..8109c8691 100644 --- a/modules/ui/angular.json +++ b/modules/ui/angular.json @@ -38,8 +38,8 @@ }, { "type": "anyComponentStyle", - "maximumWarning": "3kb", - "maximumError": "4kb" + "maximumWarning": "5kb", + "maximumError": "6kb" } ], "outputHashing": "all" diff --git a/modules/ui/build.Dockerfile b/modules/ui/build.Dockerfile index 180ad9747..082efbbcc 100644 --- a/modules/ui/build.Dockerfile +++ b/modules/ui/build.Dockerfile @@ -16,5 +16,4 @@ FROM node@sha256:ffebb4405810c92d267a764b21975fb2d96772e41877248a37bf3abaa0d3b590 as build # Set the working directory -WORKDIR /modules/ui - +WORKDIR /modules/ui \ No newline at end of file diff --git a/modules/ui/package-lock.json b/modules/ui/package-lock.json index 637dc47e4..7d3fb5c5c 100644 --- a/modules/ui/package-lock.json +++ b/modules/ui/package-lock.json @@ -8,37 +8,37 @@ "name": "test-run-ui", "version": "0.0.0", "dependencies": { - "@angular/animations": "^17.0.8", - "@angular/cdk": "^17.0.4", - "@angular/common": "^17.0.8", - "@angular/compiler": "^17.0.8", - "@angular/core": "^17.0.8", - "@angular/forms": "^17.3.1", - "@angular/material": "^17.3.1", - "@angular/platform-browser": "^17.0.8", - "@angular/platform-browser-dynamic": "^17.3.1", - "@angular/router": "^17.3.1", - "@ngrx/component-store": "^17.1.1", - "@ngrx/effects": "^17.1.1", - "@ngrx/store": "^17.0.1", + "@angular/animations": "^18.2.4", + "@angular/cdk": "^18.2.0", + "@angular/common": "^18.2.4", + "@angular/compiler": "^18.2.4", + "@angular/core": "^18.2.4", + "@angular/forms": "^18.2.4", + "@angular/material": "^18.2.0", + "@angular/platform-browser": "^18.2.4", + "@angular/platform-browser-dynamic": "^18.2.4", + "@angular/router": "^18.2.4", + "@ngrx/component-store": "^18.0.2", + "@ngrx/effects": "^18.0.2", + "@ngrx/store": "^18.0.2", "ngx-mask": "^16.4.2", "ngx-mqtt": "^17.0.0", "rxjs": "~7.8.0", "tslib": "^2.6.2", - "zone.js": "^0.14.4" + "zone.js": "^0.14.10" }, "devDependencies": { - "@angular-devkit/build-angular": "^17.3.2", - "@angular-eslint/builder": "17.2.0", - "@angular-eslint/eslint-plugin": "17.2.0", - "@angular-eslint/eslint-plugin-template": "17.2.0", - "@angular-eslint/schematics": "17.2.0", - "@angular-eslint/template-parser": "17.2.0", - "@angular/cli": "~17.0.9", - "@angular/compiler-cli": "^17.3.1", + "@angular-devkit/build-angular": "^18.1.4", + "@angular-eslint/builder": "18.3.0", + "@angular-eslint/eslint-plugin": "^18.3.0", + "@angular-eslint/eslint-plugin-template": "^18.3.0", + "@angular-eslint/schematics": "^18.3.0", + "@angular-eslint/template-parser": "18.3.0", + "@angular/cli": "~18.2.4", + "@angular/compiler-cli": "^18.2.4", "@types/jasmine": "~4.3.6", - "@typescript-eslint/eslint-plugin": "^6.21.0", - "@typescript-eslint/parser": "^6.21.0", + "@typescript-eslint/eslint-plugin": "^8.2.0", + "@typescript-eslint/parser": "^8.2.0", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", @@ -49,8 +49,8 @@ "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", "prettier": "^3.2.5", - "prettier-eslint": "^16.3.0", - "typescript": "~5.2.2" + "prettier-eslint": "^13.0.0", + "typescript": "~5.5.4" } }, "node_modules/@ampproject/remapping": { @@ -67,112 +67,111 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1703.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1703.6.tgz", - "integrity": "sha512-Ck501FD/QuOjeKVFs7hU92w8+Ffetv0d5Sq09XY2/uygo5c/thMzp9nkevaIWBxUSeU5RqYZizDrhFVgYzbbOw==", + "version": "0.1802.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.6.tgz", + "integrity": "sha512-oF7cPFdTLxeuvXkK/opSdIxZ1E4LrBbmuytQ/nCoAGOaKBWdqvwagRZ6jVhaI0Gwu48rkcV7Zhesg/ESNnROdw==", "dev": true, "dependencies": { - "@angular-devkit/core": "17.3.6", + "@angular-devkit/core": "18.2.6", "rxjs": "7.8.1" }, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, "node_modules/@angular-devkit/build-angular": { - "version": "17.3.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-17.3.6.tgz", - "integrity": "sha512-K4CEZvhQZUUOpmXPVoI1YBM8BARbIlqE6FZRxakmnr+YOtVTYE5s+Dr1wgja8hZIohNz6L7j167G9Aut7oPU/w==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.6.tgz", + "integrity": "sha512-u12cJZttgs5j7gICHWSmcaTCu0EFXEzKqI8nkYCwq2MtuJlAXiMQSXYuEP9OU3Go4vMAPtQh2kShyOWCX5b4EQ==", "dev": true, "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1703.6", - "@angular-devkit/build-webpack": "0.1703.6", - "@angular-devkit/core": "17.3.6", - "@babel/core": "7.24.0", - "@babel/generator": "7.23.6", - "@babel/helper-annotate-as-pure": "7.22.5", - "@babel/helper-split-export-declaration": "7.22.6", - "@babel/plugin-transform-async-generator-functions": "7.23.9", - "@babel/plugin-transform-async-to-generator": "7.23.3", - "@babel/plugin-transform-runtime": "7.24.0", - "@babel/preset-env": "7.24.0", - "@babel/runtime": "7.24.0", - "@discoveryjs/json-ext": "0.5.7", - "@ngtools/webpack": "17.3.6", + "@angular-devkit/architect": "0.1802.6", + "@angular-devkit/build-webpack": "0.1802.6", + "@angular-devkit/core": "18.2.6", + "@angular/build": "18.2.6", + "@babel/core": "7.25.2", + "@babel/generator": "7.25.0", + "@babel/helper-annotate-as-pure": "7.24.7", + "@babel/helper-split-export-declaration": "7.24.7", + "@babel/plugin-transform-async-generator-functions": "7.25.0", + "@babel/plugin-transform-async-to-generator": "7.24.7", + "@babel/plugin-transform-runtime": "7.24.7", + "@babel/preset-env": "7.25.3", + "@babel/runtime": "7.25.0", + "@discoveryjs/json-ext": "0.6.1", + "@ngtools/webpack": "18.2.6", "@vitejs/plugin-basic-ssl": "1.1.0", "ansi-colors": "4.1.3", - "autoprefixer": "10.4.18", + "autoprefixer": "10.4.20", "babel-loader": "9.1.3", - "babel-plugin-istanbul": "6.1.1", "browserslist": "^4.21.5", - "copy-webpack-plugin": "11.0.0", - "critters": "0.0.22", - "css-loader": "6.10.0", - "esbuild-wasm": "0.20.1", + "copy-webpack-plugin": "12.0.2", + "critters": "0.0.24", + "css-loader": "7.1.2", + "esbuild-wasm": "0.23.0", "fast-glob": "3.3.2", - "http-proxy-middleware": "2.0.6", - "https-proxy-agent": "7.0.4", - "inquirer": "9.2.15", - "jsonc-parser": "3.2.1", + "http-proxy-middleware": "3.0.0", + "https-proxy-agent": "7.0.5", + "istanbul-lib-instrument": "6.0.3", + "jsonc-parser": "3.3.1", "karma-source-map-support": "1.4.0", "less": "4.2.0", - "less-loader": "11.1.0", + "less-loader": "12.2.0", "license-webpack-plugin": "4.0.2", - "loader-utils": "3.2.1", - "magic-string": "0.30.8", - "mini-css-extract-plugin": "2.8.1", + "loader-utils": "3.3.1", + "magic-string": "0.30.11", + "mini-css-extract-plugin": "2.9.0", "mrmime": "2.0.0", - "open": "8.4.2", + "open": "10.1.0", "ora": "5.4.1", "parse5-html-rewriting-stream": "7.0.0", - "picomatch": "4.0.1", - "piscina": "4.4.0", - "postcss": "8.4.35", + "picomatch": "4.0.2", + "piscina": "4.6.1", + "postcss": "8.4.41", "postcss-loader": "8.1.1", "resolve-url-loader": "5.0.0", "rxjs": "7.8.1", - "sass": "1.71.1", - "sass-loader": "14.1.1", - "semver": "7.6.0", + "sass": "1.77.6", + "sass-loader": "16.0.0", + "semver": "7.6.3", "source-map-loader": "5.0.0", "source-map-support": "0.5.21", - "terser": "5.29.1", + "terser": "5.31.6", "tree-kill": "1.2.2", - "tslib": "2.6.2", - "undici": "6.11.1", - "vite": "5.1.7", - "watchpack": "2.4.0", - "webpack": "5.90.3", - "webpack-dev-middleware": "6.1.2", - "webpack-dev-server": "4.15.1", - "webpack-merge": "5.10.0", + "tslib": "2.6.3", + "vite": "5.4.6", + "watchpack": "2.4.1", + "webpack": "5.94.0", + "webpack-dev-middleware": "7.4.2", + "webpack-dev-server": "5.0.4", + "webpack-merge": "6.0.1", "webpack-subresource-integrity": "5.1.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, "optionalDependencies": { - "esbuild": "0.20.1" + "esbuild": "0.23.0" }, "peerDependencies": { - "@angular/compiler-cli": "^17.0.0", - "@angular/localize": "^17.0.0", - "@angular/platform-server": "^17.0.0", - "@angular/service-worker": "^17.0.0", + "@angular/compiler-cli": "^18.0.0", + "@angular/localize": "^18.0.0", + "@angular/platform-server": "^18.0.0", + "@angular/service-worker": "^18.0.0", "@web/test-runner": "^0.18.0", "browser-sync": "^3.0.2", "jest": "^29.5.0", "jest-environment-jsdom": "^29.5.0", "karma": "^6.3.0", - "ng-packagr": "^17.0.0", + "ng-packagr": "^18.0.0", "protractor": "^7.0.0", "tailwindcss": "^2.0.0 || ^3.0.0", - "typescript": ">=5.2 <5.5" + "typescript": ">=5.4 <5.6" }, "peerDependenciesMeta": { "@angular/localize": { @@ -210,40 +209,46 @@ } } }, + "node_modules/@angular-devkit/build-angular/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true + }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1703.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1703.6.tgz", - "integrity": "sha512-pJu0et2SiF0kfXenHSTtAART0omzbWpLgBfeUo4hBh4uwX5IaT+mRpYpr8gCXMq+qsjoQp3HobSU3lPDeBn+bg==", + "version": "0.1802.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.6.tgz", + "integrity": "sha512-JMLcXFaitJplwZMKkqhbYirINCRD6eOPZuIGaIOVynXYGWgvJkLT9t5C2wm9HqSLtp1K7NcYG2Y7PtTVR4krnQ==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1703.6", + "@angular-devkit/architect": "0.1802.6", "rxjs": "7.8.1" }, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, "peerDependencies": { "webpack": "^5.30.0", - "webpack-dev-server": "^4.0.0" + "webpack-dev-server": "^5.0.2" } }, "node_modules/@angular-devkit/core": { - "version": "17.3.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.6.tgz", - "integrity": "sha512-FVbkT9dEwHEvjnxr4mvMNSMg2bCFoGoP4X68xXU9dhLEUpC05opLvfbaR3Qh543eCJ5AstosBFVzB/krfIkOvA==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.6.tgz", + "integrity": "sha512-la4CFvs5PcRWSkQ/H7TB5cPZirFVA9GoWk5LzIk8si6VjWBJRm8b3keKJoC9LlNeABRUIR5z0ocYkyQQUhdMfg==", "dev": true, "dependencies": { - "ajv": "8.12.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.1", - "picomatch": "4.0.1", + "ajv": "8.17.1", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", "rxjs": "7.8.1", "source-map": "0.7.4" }, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, @@ -257,440 +262,280 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "17.0.10", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.0.10.tgz", - "integrity": "sha512-hjf4gaMx2uB6ZhBstBSH0Q2hzfp6kxI4IiJ5i1QrxPNE1MdGnb2h+LgPTRCdO72a7PGeWcSxFRE7cxrXeQy19g==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.6.tgz", + "integrity": "sha512-uIttrQ2cQ2PWAFFVPeCoNR8xvs7tPJ2i8gzqsIwYdge107xDC6u9CqfgmBqPDSFpWj+IiC2Jwcm8Z4HYKU4+7A==", "dev": true, "dependencies": { - "@angular-devkit/core": "17.0.10", - "jsonc-parser": "3.2.0", - "magic-string": "0.30.5", + "@angular-devkit/core": "18.2.6", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.11", "ora": "5.4.1", "rxjs": "7.8.1" }, "engines": { - "node": "^18.13.0 || >=20.9.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { - "version": "17.0.10", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.10.tgz", - "integrity": "sha512-93N6oHnmtRt0hL3AXxvnk47sN1rHndfj+pqI5haEY41AGWzIWv9cSBsqlM0PWltNpo6VivcExZESvbLJ71wqbQ==", - "dev": true, - "dependencies": { - "ajv": "8.12.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.0", - "picomatch": "3.0.1", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/schematics/node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, - "node_modules/@angular-devkit/schematics/node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/picomatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", - "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/@angular-eslint/builder": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-17.2.0.tgz", - "integrity": "sha512-xPxgCTPcnFRT8OYs9R5UZVAtzVouIIfdMOqTcB847Cev4H8kqRz0gO5aqkQiL+0erwnLf8D4nRzMTJjSBpQQNw==", + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-18.3.0.tgz", + "integrity": "sha512-httEQyqyBw3+0CRtAa7muFxHrauRfkEfk/jmrh5fn2Eiu+I53hAqFPgrwVi1V6AP/kj2zbAiWhd5xM3pMJdoRQ==", "dev": true, - "dependencies": { - "@nx/devkit": "17.2.8", - "nx": "17.2.8" - }, "peerDependencies": { - "eslint": "^7.20.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "node_modules/@angular-eslint/bundled-angular-compiler": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-17.2.0.tgz", - "integrity": "sha512-uBvPbPE2JxqpdLs//Nd5+TRLgjxDxvTYgmGFTKI9Eo98krqps+rhSQCRSHWACukzc25X3Q4ITHfvjODQL8qQkg==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.3.1.tgz", + "integrity": "sha512-sikmkjfsXPpPTku1aQkQ1MNNEKGBgGGRvUN/WeNS9dhCJ4dxU3O7dZctt1aQWj+W3nbuUtDiimAWF5fZHGFE2Q==", "dev": true }, "node_modules/@angular-eslint/eslint-plugin": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-17.2.0.tgz", - "integrity": "sha512-8A3hD/11N6QEchsAGggTPmNsa0GS5p44t930slMsxrTvdSlKAo56FzVdxwSkOcejKIJs57oWxoKvtK4UyLYkeA==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-18.3.1.tgz", + "integrity": "sha512-MP4Nm+SHboF8KdnN0KpPEGAaTTzDLPm3+S/4W3Mg8onqWCyadyd4mActh9mK/pvCj8TVlb/SW1zeTtdMYhwonw==", "dev": true, "dependencies": { - "@angular-eslint/utils": "17.2.0", - "@typescript-eslint/utils": "6.18.0" + "@angular-eslint/bundled-angular-compiler": "18.3.1", + "@angular-eslint/utils": "18.3.1" }, "peerDependencies": { - "eslint": "^7.20.0 || ^8.0.0", + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "node_modules/@angular-eslint/eslint-plugin-template": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-17.2.0.tgz", - "integrity": "sha512-CkcAOWWqNwX0FXeLwJu0Vctso8q/NPHJ95R2Cy8hjwuMyFF83/vDouyeIjYC+SRv6hbevmNa+BbdYXhQZinIHw==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-18.3.1.tgz", + "integrity": "sha512-hBJ3+f7VSidvrtYaXH7Vp0sWvblA9jLK2c6uQzhYGWdEDUcTg7g7VI9ThW39WvMbHqkyzNE4PPOynK69cBEDGg==", "dev": true, "dependencies": { - "@angular-eslint/bundled-angular-compiler": "17.2.0", - "@angular-eslint/utils": "17.2.0", - "@typescript-eslint/type-utils": "6.18.0", - "@typescript-eslint/utils": "6.18.0", + "@angular-eslint/bundled-angular-compiler": "18.3.1", + "@angular-eslint/utils": "18.3.1", "aria-query": "5.3.0", - "axobject-query": "4.0.0" + "axobject-query": "4.1.0" }, "peerDependencies": { - "eslint": "^7.20.0 || ^8.0.0", + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "node_modules/@angular-eslint/schematics": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-17.2.0.tgz", - "integrity": "sha512-lV2+2H3Hf6FCJfM+cddJ/7ss3qc99OO2wuvTjGNH512mP75tvfLakV+e6TFFdzK0km+ceXvB2VqNXMSShB4PVQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-18.3.1.tgz", + "integrity": "sha512-BTsQHDu7LjvXannJTb5BqMPCFIHRNN94eRyb60VfjJxB/ZFtsbAQDFFOi5lEZsRsd4mBeUMuL9mW4IMcPtUQ9Q==", "dev": true, "dependencies": { - "@angular-eslint/eslint-plugin": "17.2.0", - "@angular-eslint/eslint-plugin-template": "17.2.0", - "@nx/devkit": "17.2.8", - "ignore": "5.3.0", - "nx": "17.2.8", - "strip-json-comments": "3.1.1", - "tmp": "0.2.1" + "@angular-eslint/eslint-plugin": "18.3.1", + "@angular-eslint/eslint-plugin-template": "18.3.1", + "ignore": "5.3.2", + "semver": "7.6.3", + "strip-json-comments": "3.1.1" }, "peerDependencies": { - "@angular/cli": ">= 17.0.0 < 18.0.0" + "@angular-devkit/core": ">= 18.0.0 < 19.0.0", + "@angular-devkit/schematics": ">= 18.0.0 < 19.0.0" } }, "node_modules/@angular-eslint/template-parser": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-17.2.0.tgz", - "integrity": "sha512-Js9w1IXWPvXEjd05bWkZzRaLw0g0mJPztAWOj3DiU7H9LKkautQq0zZu02cAAnXZim2CsAagEh2GmGjhaYvoKg==", + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-18.3.0.tgz", + "integrity": "sha512-1mUquqcnugI4qsoxcYZKZ6WMi6RPelDcJZg2YqGyuaIuhWmi3ZqJZLErSSpjP60+TbYZu7wM8Kchqa1bwJtEaQ==", "dev": true, "dependencies": { - "@angular-eslint/bundled-angular-compiler": "17.2.0", - "eslint-scope": "^8.0.0" + "@angular-eslint/bundled-angular-compiler": "18.3.0", + "eslint-scope": "^8.0.2" }, "peerDependencies": { - "eslint": "^7.20.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, + "node_modules/@angular-eslint/template-parser/node_modules/@angular-eslint/bundled-angular-compiler": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.3.0.tgz", + "integrity": "sha512-v/59FxUKnMzymVce99gV43huxoqXWMb85aKvzlNvLN+ScDu6ZE4YMiTQNpfapVL2lkxhs0uwB3jH17EYd5TcsA==", + "dev": true + }, "node_modules/@angular-eslint/utils": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-17.2.0.tgz", - "integrity": "sha512-J7DsFKb5yxv8te8LQvChNn6MBvKulcBx+jtHX1uen+uuuv8XhZuVMZXS0rolUkdl1Q0mBeHpkuO2q6Vh17pqbQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-18.3.1.tgz", + "integrity": "sha512-sd9niZI7h9H2FQ7OLiQsLFBhjhRQTASh+Q0+4+hyjv9idbSHBJli8Gsi2fqj9zhtMKpAZFTrWzuLUpubJ9UYbA==", "dev": true, "dependencies": { - "@angular-eslint/bundled-angular-compiler": "17.2.0", - "@typescript-eslint/utils": "6.18.0" + "@angular-eslint/bundled-angular-compiler": "18.3.1" }, "peerDependencies": { - "eslint": "^7.20.0 || ^8.0.0", + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "node_modules/@angular/animations": { - "version": "17.3.7", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-17.3.7.tgz", - "integrity": "sha512-ahenGALPPweeHgqtl9BMkGIAV4fUNI5kOWUrLNbKBfwIJN+aOBOYV1Jz6NKUQq6eYn/1ZYtm0f3lIkHIdtLKEw==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.2.6.tgz", + "integrity": "sha512-vy9wy+Q9beiRxkEO8wNxFQ63AqAujGvk8AUHepxxIT7QNNc512TNKz8uH+feWDPO38Dm2obwYQHMGzs3WO7pUA==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "17.3.7" - } - }, - "node_modules/@angular/cdk": { - "version": "17.3.7", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-17.3.7.tgz", - "integrity": "sha512-aFEh8tzKFOwini6aNEp57S54Ocp9T7YIJfBVMESptu2TCPdMTlJ1HJTg5XS8NcQO+vwi9cFPGVwGF1frOx4LXA==", - "dependencies": { - "tslib": "^2.3.0" - }, - "optionalDependencies": { - "parse5": "^7.1.2" - }, - "peerDependencies": { - "@angular/common": "^17.0.0 || ^18.0.0", - "@angular/core": "^17.0.0 || ^18.0.0", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, - "node_modules/@angular/cli": { - "version": "17.0.10", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-17.0.10.tgz", - "integrity": "sha512-52rd8KmOMe3NJDp/wA+Mwj21qd4HR8fuLtfrErgVnZaJZKX2Bzi/z7FHQD3gdgMAdzUiG0OJWGM0h75Ls9X6Gw==", - "dev": true, - "dependencies": { - "@angular-devkit/architect": "0.1700.10", - "@angular-devkit/core": "17.0.10", - "@angular-devkit/schematics": "17.0.10", - "@schematics/angular": "17.0.10", - "@yarnpkg/lockfile": "1.1.0", - "ansi-colors": "4.1.3", - "ini": "4.1.1", - "inquirer": "9.2.11", - "jsonc-parser": "3.2.0", - "npm-package-arg": "11.0.1", - "npm-pick-manifest": "9.0.0", - "open": "8.4.2", - "ora": "5.4.1", - "pacote": "17.0.4", - "resolve": "1.22.8", - "semver": "7.5.4", - "symbol-observable": "4.0.0", - "yargs": "17.7.2" - }, - "bin": { - "ng": "bin/ng.js" - }, - "engines": { - "node": "^18.13.0 || >=20.9.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { - "version": "0.1700.10", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1700.10.tgz", - "integrity": "sha512-JD/3jkdN1jrFMIDEk9grKdbjutIoxUDMRazq1LZooWjTkzlYk09i/s6HwvIPao7zvxJfelD6asTPspgkjOMP5A==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "17.0.10", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.13.0 || >=20.9.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "@angular/core": "18.2.6" } }, - "node_modules/@angular/cli/node_modules/@angular-devkit/core": { - "version": "17.0.10", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.10.tgz", - "integrity": "sha512-93N6oHnmtRt0hL3AXxvnk47sN1rHndfj+pqI5haEY41AGWzIWv9cSBsqlM0PWltNpo6VivcExZESvbLJ71wqbQ==", + "node_modules/@angular/build": { + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.6.tgz", + "integrity": "sha512-TQzX6Mi7uXFvmz7+OVl4Za7WawYPcx+B5Ewm6IY/DdMyB9P/Z4tbKb1LO+ynWUXYwm7avXo6XQQ4m5ArDY5F/A==", "dev": true, "dependencies": { - "ajv": "8.12.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.0", - "picomatch": "3.0.1", - "rxjs": "7.8.1", - "source-map": "0.7.4" + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.1802.6", + "@babel/core": "7.25.2", + "@babel/helper-annotate-as-pure": "7.24.7", + "@babel/helper-split-export-declaration": "7.24.7", + "@babel/plugin-syntax-import-attributes": "7.24.7", + "@inquirer/confirm": "3.1.22", + "@vitejs/plugin-basic-ssl": "1.1.0", + "browserslist": "^4.23.0", + "critters": "0.0.24", + "esbuild": "0.23.0", + "fast-glob": "3.3.2", + "https-proxy-agent": "7.0.5", + "listr2": "8.2.4", + "lmdb": "3.0.13", + "magic-string": "0.30.11", + "mrmime": "2.0.0", + "parse5-html-rewriting-stream": "7.0.0", + "picomatch": "4.0.2", + "piscina": "4.6.1", + "rollup": "4.22.4", + "sass": "1.77.6", + "semver": "7.6.3", + "vite": "5.4.6", + "watchpack": "2.4.1" }, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, "peerDependencies": { - "chokidar": "^3.5.2" + "@angular/compiler-cli": "^18.0.0", + "@angular/localize": "^18.0.0", + "@angular/platform-server": "^18.0.0", + "@angular/service-worker": "^18.0.0", + "less": "^4.2.0", + "postcss": "^8.4.0", + "tailwindcss": "^2.0.0 || ^3.0.0", + "typescript": ">=5.4 <5.6" }, "peerDependenciesMeta": { - "chokidar": { + "@angular/localize": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "less": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tailwindcss": { "optional": true } } }, - "node_modules/@angular/cli/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@angular/cli/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular/cli/node_modules/figures": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", - "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^5.0.0", - "is-unicode-supported": "^1.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular/cli/node_modules/inquirer": { - "version": "9.2.11", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.11.tgz", - "integrity": "sha512-B2LafrnnhbRzCWfAdOXisUzL89Kg8cVJlYmhqoi3flSiV/TveO+nsXwgKr9h9PIo+J1hz7nBSk6gegRIMBBf7g==", - "dev": true, - "dependencies": { - "@ljharb/through": "^2.3.9", - "ansi-escapes": "^4.3.2", - "chalk": "^5.3.0", - "cli-cursor": "^3.1.0", - "cli-width": "^4.1.0", - "external-editor": "^3.1.0", - "figures": "^5.0.0", - "lodash": "^4.17.21", - "mute-stream": "1.0.0", - "ora": "^5.4.1", - "run-async": "^3.0.0", - "rxjs": "^7.8.1", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^6.2.0" - }, - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/@angular/cli/node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular/cli/node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, - "node_modules/@angular/cli/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "node_modules/@angular/cdk": { + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.6.tgz", + "integrity": "sha512-Gfq/iv4zhlKYpdQkDaBRwxI71NHNUHM1Cs1XhnZ0/oFct5HXvSv1RHRGTKqBJLLACaAPzZKXJ/UglLoyO5CNiQ==", "dependencies": { - "yallist": "^4.0.0" + "tslib": "^2.3.0" }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@angular/cli/node_modules/picomatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", - "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", - "dev": true, - "engines": { - "node": ">=10" + "optionalDependencies": { + "parse5": "^7.1.2" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "peerDependencies": { + "@angular/common": "^18.0.0 || ^19.0.0", + "@angular/core": "^18.0.0 || ^19.0.0", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@angular/cli/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "node_modules/@angular/cli": { + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.6.tgz", + "integrity": "sha512-tdXsnV/w+Rgu8q0zFsLU5L9ImTVqrTol1vppHaQkJ/vuoHy+s8ZEbBqhVrO/ffosNb2xseUybGYvqMS4zkNQjg==", "dev": true, "dependencies": { - "lru-cache": "^6.0.0" + "@angular-devkit/architect": "0.1802.6", + "@angular-devkit/core": "18.2.6", + "@angular-devkit/schematics": "18.2.6", + "@inquirer/prompts": "5.3.8", + "@listr2/prompt-adapter-inquirer": "2.0.15", + "@schematics/angular": "18.2.6", + "@yarnpkg/lockfile": "1.1.0", + "ini": "4.1.3", + "jsonc-parser": "3.3.1", + "listr2": "8.2.4", + "npm-package-arg": "11.0.3", + "npm-pick-manifest": "9.1.0", + "pacote": "18.0.6", + "resolve": "1.22.8", + "semver": "7.6.3", + "symbol-observable": "4.0.0", + "yargs": "17.7.2" }, "bin": { - "semver": "bin/semver.js" + "ng": "bin/ng.js" }, "engines": { - "node": ">=10" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" } }, - "node_modules/@angular/cli/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@angular/common": { - "version": "17.3.7", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-17.3.7.tgz", - "integrity": "sha512-A7LRJu1vVCGGgrfZXjU+njz50SiU4weheKCar5PIUprcdIofS1IrHAJDqYh+kwXxkjXbZMOr/ijQY0+AESLEsw==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-18.2.6.tgz", + "integrity": "sha512-89793ow+wrI1c7C6kyMbnweLNIZHzXthosxAEjipRZGBrqBYjvTtkE45Fl+5yBa3JO7bAhyGkUnEoyvWtZIAEA==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "17.3.7", + "@angular/core": "18.2.6", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "17.3.7", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-17.3.7.tgz", - "integrity": "sha512-AlKiqPoxnrpQ0hn13fIaQPSVodaVAIjBW4vpFyuKFqs2LBKg6iolwZ21s8rEI0KR2gXl+8ugj0/UZ6YADiVM5w==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-18.2.6.tgz", + "integrity": "sha512-3tX2/Qw+bZ8XzKitviH8jzNGyY0uohhehhBB57OJOCc+yr4ojy/7SYFnun1lSsRnDztdCE461641X4iQLCQ94w==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "17.3.7" + "@angular/core": "18.2.6" }, "peerDependenciesMeta": { "@angular/core": { @@ -699,12 +544,12 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "17.3.7", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.3.7.tgz", - "integrity": "sha512-vSg5IQZ9jGmvYjpbfH8KbH4Sl1IVeE+Mr1ogcxkGEsURSRvKo7EWc0K7LSEI9+gg0VLamMiP9EyCJdPxiJeLJQ==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-18.2.6.tgz", + "integrity": "sha512-b5x9STfjNiNM/S0D+CnqRP9UOxPtSz1+RlCH5WdOMiW/p8j5p6dBix8YYgTe6Wg3OD7eItD2pnFQKgF/dWiopA==", "dev": true, "dependencies": { - "@babel/core": "7.23.9", + "@babel/core": "7.25.2", "@jridgewell/sourcemap-codec": "^1.4.14", "chokidar": "^3.0.0", "convert-source-map": "^1.5.1", @@ -719,168 +564,76 @@ "ngcc": "bundles/ngcc/index.js" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/compiler": "17.3.7", - "typescript": ">=5.2 <5.5" - } - }, - "node_modules/@angular/compiler-cli/node_modules/@babel/core": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", - "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.9", - "@babel/parser": "^7.23.9", - "@babel/template": "^7.23.9", - "@babel/traverse": "^7.23.9", - "@babel/types": "^7.23.9", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@angular/compiler-cli/node_modules/@babel/core/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "node_modules/@angular/compiler-cli/node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "@angular/compiler": "18.2.6", + "typescript": ">=5.4 <5.6" } }, "node_modules/@angular/core": { - "version": "17.3.7", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-17.3.7.tgz", - "integrity": "sha512-HWcrbxqnvIMSxFuQdN0KPt08bc87hqr0LKm89yuRTUwx/2sNJlNQUobk6aJj4trswGBttcRDT+GOS4DQP2Nr4g==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.6.tgz", + "integrity": "sha512-PjFad2j4YBwLVTw+0Te8CJCa/tV0W8caTHG8aOjj3ObdL6ihGI+FKnwerLc9RVzDFd14BOO4C6/+LbOQAh3Ltw==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { "rxjs": "^6.5.3 || ^7.4.0", - "zone.js": "~0.14.0" + "zone.js": "~0.14.10" } }, "node_modules/@angular/forms": { - "version": "17.3.7", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-17.3.7.tgz", - "integrity": "sha512-FEhXh/VmT++XCoO8i7bBtzxG7Am/cE1zrr9aF+fWW+4jpWvJvVN1IaSiJxgBB+iPsOJ9lTBRwfRW3onlcDkhrw==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.2.6.tgz", + "integrity": "sha512-quGkUqTxlBaLB8C/RnpfFG57fdmNF5RQ+368N89Ma++2lpIsVAHaGZZn4yOyo3wNYaM2jBxNqaYxOzZNUl5Tig==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "17.3.7", - "@angular/core": "17.3.7", - "@angular/platform-browser": "17.3.7", + "@angular/common": "18.2.6", + "@angular/core": "18.2.6", + "@angular/platform-browser": "18.2.6", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/material": { - "version": "17.3.7", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-17.3.7.tgz", - "integrity": "sha512-wjSKkk9KZE8QiBPkMd5axh5u/3pUSxoLKNO7OasFhEagMmSv5oYTLm40cErhtb4UdkSmbC19WuuluS6P3leoPA==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/auto-init": "15.0.0-canary.7f224ddd4.0", - "@material/banner": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/button": "15.0.0-canary.7f224ddd4.0", - "@material/card": "15.0.0-canary.7f224ddd4.0", - "@material/checkbox": "15.0.0-canary.7f224ddd4.0", - "@material/chips": "15.0.0-canary.7f224ddd4.0", - "@material/circular-progress": "15.0.0-canary.7f224ddd4.0", - "@material/data-table": "15.0.0-canary.7f224ddd4.0", - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/dialog": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/drawer": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/fab": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/floating-label": "15.0.0-canary.7f224ddd4.0", - "@material/form-field": "15.0.0-canary.7f224ddd4.0", - "@material/icon-button": "15.0.0-canary.7f224ddd4.0", - "@material/image-list": "15.0.0-canary.7f224ddd4.0", - "@material/layout-grid": "15.0.0-canary.7f224ddd4.0", - "@material/line-ripple": "15.0.0-canary.7f224ddd4.0", - "@material/linear-progress": "15.0.0-canary.7f224ddd4.0", - "@material/list": "15.0.0-canary.7f224ddd4.0", - "@material/menu": "15.0.0-canary.7f224ddd4.0", - "@material/menu-surface": "15.0.0-canary.7f224ddd4.0", - "@material/notched-outline": "15.0.0-canary.7f224ddd4.0", - "@material/radio": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/segmented-button": "15.0.0-canary.7f224ddd4.0", - "@material/select": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/slider": "15.0.0-canary.7f224ddd4.0", - "@material/snackbar": "15.0.0-canary.7f224ddd4.0", - "@material/switch": "15.0.0-canary.7f224ddd4.0", - "@material/tab": "15.0.0-canary.7f224ddd4.0", - "@material/tab-bar": "15.0.0-canary.7f224ddd4.0", - "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0", - "@material/tab-scroller": "15.0.0-canary.7f224ddd4.0", - "@material/textfield": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tooltip": "15.0.0-canary.7f224ddd4.0", - "@material/top-app-bar": "15.0.0-canary.7f224ddd4.0", - "@material/touch-target": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-18.2.6.tgz", + "integrity": "sha512-ObxC/vomSb9QF3vIztuiInQzws+D6u09Dhfx6uNFjtyICqxEFpF7+Qx7QVDWrsuXOgxZTKgacK8f46iV8hWUfg==", + "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { - "@angular/animations": "^17.0.0 || ^18.0.0", - "@angular/cdk": "17.3.7", - "@angular/common": "^17.0.0 || ^18.0.0", - "@angular/core": "^17.0.0 || ^18.0.0", - "@angular/forms": "^17.0.0 || ^18.0.0", - "@angular/platform-browser": "^17.0.0 || ^18.0.0", + "@angular/animations": "^18.0.0 || ^19.0.0", + "@angular/cdk": "18.2.6", + "@angular/common": "^18.0.0 || ^19.0.0", + "@angular/core": "^18.0.0 || ^19.0.0", + "@angular/forms": "^18.0.0 || ^19.0.0", + "@angular/platform-browser": "^18.0.0 || ^19.0.0", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/platform-browser": { - "version": "17.3.7", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.7.tgz", - "integrity": "sha512-Nn8ZMaftAvO9dEwribWdNv+QBHhYIBrRkv85G6et80AXfXoYAr/xcfnQECRFtZgPmANqHC5auv/xrmExQG+Yeg==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.6.tgz", + "integrity": "sha512-RA8UMiYNLga+QMwpKcDw1357gYPfPyY/rmLeezMak//BbsENFYQOJ4Z6DBOBNiPlHxmBsUJMGaKdlpQhfCROyQ==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/animations": "17.3.7", - "@angular/common": "17.3.7", - "@angular/core": "17.3.7" + "@angular/animations": "18.2.6", + "@angular/common": "18.2.6", + "@angular/core": "18.2.6" }, "peerDependenciesMeta": { "@angular/animations": { @@ -889,46 +642,46 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "17.3.7", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-17.3.7.tgz", - "integrity": "sha512-9c2I4u0L1p2v1/lW8qy+WaNHisUWbyy6wqsv2v9FfCaSM49Lxymgo9LPFPC4qEG5ei5nE+eIQ2ocRiXXsf5QkQ==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-18.2.6.tgz", + "integrity": "sha512-kGBU3FNc+DF9r33hwHZqiWoZgQbCDdEIucU0NCLCIg0Hw6/Q9Hr2ndjxQI+WynCPg0JeBn34jpouvpeJer3YDQ==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "17.3.7", - "@angular/compiler": "17.3.7", - "@angular/core": "17.3.7", - "@angular/platform-browser": "17.3.7" + "@angular/common": "18.2.6", + "@angular/compiler": "18.2.6", + "@angular/core": "18.2.6", + "@angular/platform-browser": "18.2.6" } }, "node_modules/@angular/router": { - "version": "17.3.7", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-17.3.7.tgz", - "integrity": "sha512-lMkuRrc1ZjP5JPDxNHqoAhB0uAnfPQ/q6mJrw1s8IZoVV6VyM+FxR5r13ajNcXWC38xy/YhBjpXPF1vBdxuLXg==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-18.2.6.tgz", + "integrity": "sha512-t57Sqja8unHhZlPr+4CWnQacuox2M4p2pMHps+31wt337qH6mKf4jqDmK0dE/MFdRyKjT2a2E/2NwtxXxcWNuw==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "17.3.7", - "@angular/core": "17.3.7", - "@angular/platform-browser": "17.3.7", + "@angular/common": "18.2.6", + "@angular/core": "18.2.6", + "@angular/platform-browser": "18.2.6", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" }, "engines": { @@ -936,30 +689,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", - "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz", - "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.24.0", - "@babel/parser": "^7.24.0", - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.0", - "@babel/types": "^7.24.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -990,14 +743,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", "dev": true, "dependencies": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.25.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -1005,38 +758,39 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", - "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", + "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", "dev": true, "dependencies": { - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -1054,19 +808,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz", - "integrity": "sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-member-expression-to-functions": "^7.24.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.24.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.24.5", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz", + "integrity": "sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/traverse": "^7.25.4", "semver": "^6.3.1" }, "engines": { @@ -1076,18 +828,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -1098,12 +838,12 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", - "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.2.tgz", + "integrity": "sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-annotate-as-pure": "^7.24.7", "regexpu-core": "^5.3.1", "semver": "^6.3.1" }, @@ -1139,125 +879,80 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", + "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.8" + }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "node_modules/@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dev": true, "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.5.tgz", - "integrity": "sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA==", + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", + "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", - "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz", - "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.24.3", - "@babel/helper-simple-access": "^7.24.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/helper-validator-identifier": "^7.24.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-module-transforms/node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", - "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", - "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz", + "integrity": "sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-wrap-function": "^7.22.20" + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-wrap-function": "^7.25.0", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -1267,14 +962,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", - "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", + "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5" + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -1284,103 +979,104 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz", - "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", + "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", - "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.5.tgz", - "integrity": "sha512-/xxzuNvgRl4/HLNKvnFwdhdgN3cpLxgLROeLDl83Yx0AJ1SGvq1ak0OszTOjDfiB8Vx03eJbeDWh9r+jCCWttw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz", + "integrity": "sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ==", "dev": true, "dependencies": { - "@babel/helper-function-name": "^7.23.0", - "@babel/template": "^7.24.0", - "@babel/types": "^7.24.5" + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz", - "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", + "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", "dev": true, "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5" + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", - "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -1390,10 +1086,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", - "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", "dev": true, + "dependencies": { + "@babel/types": "^7.25.6" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -1401,13 +1100,44 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.3.tgz", + "integrity": "sha512-wUrcsxZg6rqBXG05HG1FPYgsP6EvwF4WpBbxIpWIIYnH8wG0gzx3yZY3dtEHas4sTAOGkbTsc9EGPxwff8lRoA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.0.tgz", + "integrity": "sha512-Bm4bH2qsX880b/3ziJ8KD711LT7z4u8CFudmjqle65AZj/HNUFhEf90dqYv6O86buWvSBmeQDjv0Tn2aF/bIBA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz", - "integrity": "sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.0.tgz", + "integrity": "sha512-lXwdNZtTmeVOOFtwM/WDe7yg1PL8sYhRk/XH0FzbR2HDQ0xC+EnQ/JHeoMYSavtU115tnUk0q9CDyq8si+LMAA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1417,14 +1147,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz", - "integrity": "sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", + "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.24.1" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1434,13 +1164,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz", - "integrity": "sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.0.tgz", + "integrity": "sha512-tggFrk1AIShG/RUQbEwt2Tr/E+ObkfwrPjR6BjbRvsx24+PSjK8zrq0GWPNCjo8qpRx4DuJzlcvWJqlm+0h3kw==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -1525,12 +1255,12 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz", - "integrity": "sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.25.6.tgz", + "integrity": "sha512-aABl0jHw9bZ2karQ/uUD6XP4u0SG22SJrOHFoL6XB1R7dTovOP4TzTlsxOYC5yQ1pdscVK2JTUnF6QL3ARoAiQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1540,12 +1270,12 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz", - "integrity": "sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1697,12 +1427,12 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz", - "integrity": "sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", + "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1712,15 +1442,15 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.9.tgz", - "integrity": "sha512-8Q3veQEDGe14dTYuwagbRtwxQDnytyg1JFu4/HwEMETeofocrB0U0ejBJIXoeG/t2oXZ8kzCyI0ZZfbT80VFNQ==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.0.tgz", + "integrity": "sha512-uaIi2FdqzjpAMvVqvB51S42oC2JEVgh0LDsGfZVDysWE8LrJtQC2jvKmOqEYThKyB7bDEb7BP1GYWDm7tABA0Q==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.20", - "@babel/plugin-syntax-async-generators": "^7.8.4" + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-remap-async-to-generator": "^7.25.0", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -1730,14 +1460,14 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", - "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", + "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.20" + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1747,12 +1477,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz", - "integrity": "sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", + "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1762,12 +1492,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.5.tgz", - "integrity": "sha512-sMfBc3OxghjC95BkYrYocHL3NaOplrcaunblzwXhGmlPwpmfsxr4vK+mBBt49r+S240vahmv+kUxkeKgs+haCw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz", + "integrity": "sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1777,13 +1507,13 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz", - "integrity": "sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.4.tgz", + "integrity": "sha512-nZeZHyCWPfjkdU5pA/uHiTaDAFUEqkpzf1YoQT2NeSynCGYq9rxfyI3XpQbfx/a0hSnFH6TGlEXvae5Vi7GD8g==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-class-features-plugin": "^7.25.4", + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1793,13 +1523,13 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz", - "integrity": "sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", + "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.4", - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-class-static-block": "^7.14.5" }, "engines": { @@ -1810,18 +1540,16 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.5.tgz", - "integrity": "sha512-gWkLP25DFj2dwe9Ck8uwMOpko4YsqyfZJrOmqqcegeDYEbp7rmn4U6UQZNj08UF6MaX39XenSpKRCvpDRBtZ7Q==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.24.5", - "@babel/helper-replace-supers": "^7.24.1", - "@babel/helper-split-export-declaration": "^7.24.5", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.4.tgz", + "integrity": "sha512-oexUfaQle2pF/b6E0dwsxQtAol9TLSO88kQvym6HHBWFliV2lGdrPieX+WgMRLSJDVzdYywk7jXbLPuO2KLTLg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/traverse": "^7.25.4", "globals": "^11.1.0" }, "engines": { @@ -1831,26 +1559,29 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-classes/node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", + "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/template": "^7.24.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz", - "integrity": "sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==", + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", + "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/template": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1859,13 +1590,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.5.tgz", - "integrity": "sha512-SZuuLyfxvsm+Ah57I/i1HVjveBENYK9ue8MJ7qkc7ndoNjqquJiElzA7f5yaAXjyW2hKojosOTAQQRX50bPSVg==", + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", + "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1874,14 +1606,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz", - "integrity": "sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==", + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", + "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1890,28 +1621,29 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz", - "integrity": "sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==", + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.0.tgz", + "integrity": "sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz", - "integrity": "sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", + "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3" }, "engines": { @@ -1922,13 +1654,13 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz", - "integrity": "sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", + "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", "dev": true, "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1938,12 +1670,12 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz", - "integrity": "sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", + "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" }, "engines": { @@ -1954,13 +1686,13 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz", - "integrity": "sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", + "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1970,14 +1702,14 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz", - "integrity": "sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==", + "version": "7.25.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.1.tgz", + "integrity": "sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.1" }, "engines": { "node": ">=6.9.0" @@ -1987,12 +1719,12 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz", - "integrity": "sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", + "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-json-strings": "^7.8.3" }, "engines": { @@ -2003,12 +1735,12 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz", - "integrity": "sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.2.tgz", + "integrity": "sha512-HQI+HcTbm9ur3Z2DkO+jgESMAMcYLuN/A7NRw9juzxAezN9AvqvUTnpKP/9kkYANz6u7dFlAyOu44ejuGySlfw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -2018,12 +1750,12 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz", - "integrity": "sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", + "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" }, "engines": { @@ -2034,12 +1766,12 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz", - "integrity": "sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", + "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2049,13 +1781,13 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz", - "integrity": "sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", + "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2065,14 +1797,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", - "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", + "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-simple-access": "^7.22.5" + "@babel/helper-module-transforms": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-simple-access": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2082,15 +1814,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz", - "integrity": "sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz", + "integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==", "dev": true, "dependencies": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-module-transforms": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -2100,13 +1832,13 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz", - "integrity": "sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", + "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2116,13 +1848,13 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", + "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2132,12 +1864,12 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz", - "integrity": "sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", + "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2147,12 +1879,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz", - "integrity": "sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", + "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" }, "engines": { @@ -2163,12 +1895,12 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz", - "integrity": "sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", + "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-numeric-separator": "^7.10.4" }, "engines": { @@ -2179,15 +1911,15 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.5.tgz", - "integrity": "sha512-7EauQHszLGM3ay7a161tTQH7fj+3vVM/gThlz5HpFtnygTxjrlvoeq7MPVA1Vy9Q555OB8SnAOsMkLShNkkrHA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", + "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.24.5", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.24.5" + "@babel/plugin-transform-parameters": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2197,13 +1929,13 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz", - "integrity": "sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", + "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-replace-supers": "^7.24.1" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2213,12 +1945,12 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz", - "integrity": "sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", + "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" }, "engines": { @@ -2229,13 +1961,13 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.5.tgz", - "integrity": "sha512-xWCkmwKT+ihmA6l7SSTpk8e4qQl/274iNbSKRRS8mpqFR32ksy36+a+LWY8OXCCEefF8WFlnOHVsaDI2231wBg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz", + "integrity": "sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, "engines": { @@ -2246,12 +1978,12 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.5.tgz", - "integrity": "sha512-9Co00MqZ2aoky+4j2jhofErthm6QVLKbpQrvz20c3CH9KQCLHyNB+t2ya4/UrRpQGR+Wrwjg9foopoeSdnHOkA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", + "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2261,13 +1993,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz", - "integrity": "sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.4.tgz", + "integrity": "sha512-ao8BG7E2b/URaUQGqN3Tlsg+M3KlHY6rJ1O1gXAEUnZoyNQnvKyH87Kfg+FoxSeyWUB8ISZZsC91C44ZuBFytw==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-class-features-plugin": "^7.25.4", + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -2277,14 +2009,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.5.tgz", - "integrity": "sha512-JM4MHZqnWR04jPMujQDTBVRnqxpLLpx2tkn7iPn+Hmsc0Gnb79yvRWOkvqFOx3Z7P7VxiRIR22c4eGSNj87OBQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", + "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.24.5", - "@babel/helper-plugin-utils": "^7.24.5", + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, "engines": { @@ -2295,12 +2027,12 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz", - "integrity": "sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", + "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2310,12 +2042,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz", - "integrity": "sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", + "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "regenerator-transform": "^0.15.2" }, "engines": { @@ -2326,12 +2058,12 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz", - "integrity": "sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", + "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2341,16 +2073,16 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.0.tgz", - "integrity": "sha512-zc0GA5IitLKJrSfXlXmp8KDqLrnGECK7YRfQBmEKg1NmBOQ7e+KuclBEKJgzifQeUYLdNiAw4B4bjyvzWVLiSA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", + "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0", - "babel-plugin-polyfill-corejs2": "^0.4.8", - "babel-plugin-polyfill-corejs3": "^0.9.0", - "babel-plugin-polyfill-regenerator": "^0.5.5", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-regenerator": "^0.6.1", "semver": "^6.3.1" }, "engines": { @@ -2370,12 +2102,12 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz", - "integrity": "sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", + "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2385,13 +2117,13 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz", - "integrity": "sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", + "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2401,12 +2133,12 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz", - "integrity": "sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", + "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2416,12 +2148,12 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz", - "integrity": "sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", + "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2431,12 +2163,12 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.5.tgz", - "integrity": "sha512-UTGnhYVZtTAjdwOTzT+sCyXmTn8AhaxOS/MjG9REclZ6ULHWF9KoCZur0HSGU7hk8PdBFKKbYe6+gqdXWz84Jg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz", + "integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -2446,12 +2178,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz", - "integrity": "sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", + "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2461,13 +2193,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz", - "integrity": "sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", + "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2477,13 +2209,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz", - "integrity": "sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", + "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2493,13 +2225,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz", - "integrity": "sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.4.tgz", + "integrity": "sha512-qesBxiWkgN1Q+31xUE9RcMk79eOXXDCv6tfyGMRSs4RGlioSg2WVyQAm07k726cSE56pa+Kb0y9epX2qaXzTvA==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -2509,26 +2241,28 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.0.tgz", - "integrity": "sha512-ZxPEzV9IgvGn73iK0E6VB9/95Nd7aMFpbE0l8KQFDG70cOV9IxRP7Y2FUPmlK0v6ImlLqYX50iuZ3ZTVhOF2lA==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.3.tgz", + "integrity": "sha512-QsYW7UeAaXvLPX9tdVliMJE7MD7M6MLYVTovRTIwhoYQVFHR1rM4wO8wqAezYi3/BpSD+NzVCZ69R6smWiIi8g==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.25.2", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.3", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.23.3", - "@babel/plugin-syntax-import-attributes": "^7.23.3", + "@babel/plugin-syntax-import-assertions": "^7.24.7", + "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", @@ -2540,59 +2274,60 @@ "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.9", - "@babel/plugin-transform-async-to-generator": "^7.23.3", - "@babel/plugin-transform-block-scoped-functions": "^7.23.3", - "@babel/plugin-transform-block-scoping": "^7.23.4", - "@babel/plugin-transform-class-properties": "^7.23.3", - "@babel/plugin-transform-class-static-block": "^7.23.4", - "@babel/plugin-transform-classes": "^7.23.8", - "@babel/plugin-transform-computed-properties": "^7.23.3", - "@babel/plugin-transform-destructuring": "^7.23.3", - "@babel/plugin-transform-dotall-regex": "^7.23.3", - "@babel/plugin-transform-duplicate-keys": "^7.23.3", - "@babel/plugin-transform-dynamic-import": "^7.23.4", - "@babel/plugin-transform-exponentiation-operator": "^7.23.3", - "@babel/plugin-transform-export-namespace-from": "^7.23.4", - "@babel/plugin-transform-for-of": "^7.23.6", - "@babel/plugin-transform-function-name": "^7.23.3", - "@babel/plugin-transform-json-strings": "^7.23.4", - "@babel/plugin-transform-literals": "^7.23.3", - "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", - "@babel/plugin-transform-member-expression-literals": "^7.23.3", - "@babel/plugin-transform-modules-amd": "^7.23.3", - "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "@babel/plugin-transform-modules-systemjs": "^7.23.9", - "@babel/plugin-transform-modules-umd": "^7.23.3", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.23.3", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", - "@babel/plugin-transform-numeric-separator": "^7.23.4", - "@babel/plugin-transform-object-rest-spread": "^7.24.0", - "@babel/plugin-transform-object-super": "^7.23.3", - "@babel/plugin-transform-optional-catch-binding": "^7.23.4", - "@babel/plugin-transform-optional-chaining": "^7.23.4", - "@babel/plugin-transform-parameters": "^7.23.3", - "@babel/plugin-transform-private-methods": "^7.23.3", - "@babel/plugin-transform-private-property-in-object": "^7.23.4", - "@babel/plugin-transform-property-literals": "^7.23.3", - "@babel/plugin-transform-regenerator": "^7.23.3", - "@babel/plugin-transform-reserved-words": "^7.23.3", - "@babel/plugin-transform-shorthand-properties": "^7.23.3", - "@babel/plugin-transform-spread": "^7.23.3", - "@babel/plugin-transform-sticky-regex": "^7.23.3", - "@babel/plugin-transform-template-literals": "^7.23.3", - "@babel/plugin-transform-typeof-symbol": "^7.23.3", - "@babel/plugin-transform-unicode-escapes": "^7.23.3", - "@babel/plugin-transform-unicode-property-regex": "^7.23.3", - "@babel/plugin-transform-unicode-regex": "^7.23.3", - "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.0", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoped-functions": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-class-static-block": "^7.24.7", + "@babel/plugin-transform-classes": "^7.25.0", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-dotall-regex": "^7.24.7", + "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0", + "@babel/plugin-transform-dynamic-import": "^7.24.7", + "@babel/plugin-transform-exponentiation-operator": "^7.24.7", + "@babel/plugin-transform-export-namespace-from": "^7.24.7", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.25.1", + "@babel/plugin-transform-json-strings": "^7.24.7", + "@babel/plugin-transform-literals": "^7.25.2", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-member-expression-literals": "^7.24.7", + "@babel/plugin-transform-modules-amd": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-modules-systemjs": "^7.25.0", + "@babel/plugin-transform-modules-umd": "^7.24.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-new-target": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-object-super": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-property-literals": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-reserved-words": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-template-literals": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.8", + "@babel/plugin-transform-unicode-escapes": "^7.24.7", + "@babel/plugin-transform-unicode-property-regex": "^7.24.7", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.8", - "babel-plugin-polyfill-corejs3": "^0.9.0", - "babel-plugin-polyfill-regenerator": "^0.5.5", - "core-js-compat": "^3.31.0", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.37.1", "semver": "^6.3.1" }, "engines": { @@ -2632,9 +2367,9 @@ "dev": true }, "node_modules/@babel/runtime": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz", - "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", + "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" @@ -2644,33 +2379,30 @@ } }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz", - "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/types": "^7.24.5", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -2679,12 +2411,12 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", - "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5", + "@babel/types": "^7.25.6", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -2693,26 +2425,14 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/types": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", - "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.24.1", - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -2729,18 +2449,18 @@ } }, "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.1.tgz", + "integrity": "sha512-boghen8F0Q8D+0/Q1/1r6DUEieUJ8w2a1gIknExMSHBsJFOr2+0KUfHiVYBvucPwl3+RU5PFBK833FjFCh3BhA==", "dev": true, "engines": { - "node": ">=10.0.0" + "node": ">=14.17.0" } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.1.tgz", - "integrity": "sha512-m55cpeupQ2DbuRGQMMZDzbv9J9PgVelPjlcmM5kxHnrBdBx6REaEd7LamYV7Dm8N7rCyR/XwU6rVP8ploKtIkA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz", + "integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==", "cpu": [ "ppc64" ], @@ -2750,13 +2470,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.1.tgz", - "integrity": "sha512-4j0+G27/2ZXGWR5okcJi7pQYhmkVgb4D7UKwxcqrjhvp5TKWx3cUjgB1CGj1mfdmJBQ9VnUGgUhign+FPF2Zgw==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.0.tgz", + "integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==", "cpu": [ "arm" ], @@ -2766,13 +2486,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.1.tgz", - "integrity": "sha512-hCnXNF0HM6AjowP+Zou0ZJMWWa1VkD77BXe959zERgGJBBxB+sV+J9f/rcjeg2c5bsukD/n17RKWXGFCO5dD5A==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz", + "integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==", "cpu": [ "arm64" ], @@ -2782,13 +2502,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.1.tgz", - "integrity": "sha512-MSfZMBoAsnhpS+2yMFYIQUPs8Z19ajwfuaSZx+tSl09xrHZCjbeXXMsUF/0oq7ojxYEpsSo4c0SfjxOYXRbpaA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.0.tgz", + "integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==", "cpu": [ "x64" ], @@ -2798,13 +2518,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.1.tgz", - "integrity": "sha512-Ylk6rzgMD8klUklGPzS414UQLa5NPXZD5tf8JmQU8GQrj6BrFA/Ic9tb2zRe1kOZyCbGl+e8VMbDRazCEBqPvA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz", + "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==", "cpu": [ "arm64" ], @@ -2814,13 +2534,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.1.tgz", - "integrity": "sha512-pFIfj7U2w5sMp52wTY1XVOdoxw+GDwy9FsK3OFz4BpMAjvZVs0dT1VXs8aQm22nhwoIWUmIRaE+4xow8xfIDZA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz", + "integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==", "cpu": [ "x64" ], @@ -2830,13 +2550,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.1.tgz", - "integrity": "sha512-UyW1WZvHDuM4xDz0jWun4qtQFauNdXjXOtIy7SYdf7pbxSWWVlqhnR/T2TpX6LX5NI62spt0a3ldIIEkPM6RHw==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz", + "integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==", "cpu": [ "arm64" ], @@ -2846,13 +2566,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.1.tgz", - "integrity": "sha512-itPwCw5C+Jh/c624vcDd9kRCCZVpzpQn8dtwoYIt2TJF3S9xJLiRohnnNrKwREvcZYx0n8sCSbvGH349XkcQeg==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz", + "integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==", "cpu": [ "x64" ], @@ -2862,13 +2582,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.1.tgz", - "integrity": "sha512-LojC28v3+IhIbfQ+Vu4Ut5n3wKcgTu6POKIHN9Wpt0HnfgUGlBuyDDQR4jWZUZFyYLiz4RBBBmfU6sNfn6RhLw==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz", + "integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==", "cpu": [ "arm" ], @@ -2878,13 +2598,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.1.tgz", - "integrity": "sha512-cX8WdlF6Cnvw/DO9/X7XLH2J6CkBnz7Twjpk56cshk9sjYVcuh4sXQBy5bmTwzBjNVZze2yaV1vtcJS04LbN8w==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz", + "integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==", "cpu": [ "arm64" ], @@ -2894,13 +2614,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.1.tgz", - "integrity": "sha512-4H/sQCy1mnnGkUt/xszaLlYJVTz3W9ep52xEefGtd6yXDQbz/5fZE5dFLUgsPdbUOQANcVUa5iO6g3nyy5BJiw==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz", + "integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==", "cpu": [ "ia32" ], @@ -2910,13 +2630,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.1.tgz", - "integrity": "sha512-c0jgtB+sRHCciVXlyjDcWb2FUuzlGVRwGXgI+3WqKOIuoo8AmZAddzeOHeYLtD+dmtHw3B4Xo9wAUdjlfW5yYA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz", + "integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==", "cpu": [ "loong64" ], @@ -2926,13 +2646,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.1.tgz", - "integrity": "sha512-TgFyCfIxSujyuqdZKDZ3yTwWiGv+KnlOeXXitCQ+trDODJ+ZtGOzLkSWngynP0HZnTsDyBbPy7GWVXWaEl6lhA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz", + "integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==", "cpu": [ "mips64el" ], @@ -2942,13 +2662,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.1.tgz", - "integrity": "sha512-b+yuD1IUeL+Y93PmFZDZFIElwbmFfIKLKlYI8M6tRyzE6u7oEP7onGk0vZRh8wfVGC2dZoy0EqX1V8qok4qHaw==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz", + "integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==", "cpu": [ "ppc64" ], @@ -2958,13 +2678,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.1.tgz", - "integrity": "sha512-wpDlpE0oRKZwX+GfomcALcouqjjV8MIX8DyTrxfyCfXxoKQSDm45CZr9fanJ4F6ckD4yDEPT98SrjvLwIqUCgg==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz", + "integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==", "cpu": [ "riscv64" ], @@ -2974,13 +2694,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.1.tgz", - "integrity": "sha512-5BepC2Au80EohQ2dBpyTquqGCES7++p7G+7lXe1bAIvMdXm4YYcEfZtQrP4gaoZ96Wv1Ute61CEHFU7h4FMueQ==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz", + "integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==", "cpu": [ "s390x" ], @@ -2990,13 +2710,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.1.tgz", - "integrity": "sha512-5gRPk7pKuaIB+tmH+yKd2aQTRpqlf1E4f/mC+tawIm/CGJemZcHZpp2ic8oD83nKgUPMEd0fNanrnFljiruuyA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz", + "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==", "cpu": [ "x64" ], @@ -3006,13 +2726,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.1.tgz", - "integrity": "sha512-4fL68JdrLV2nVW2AaWZBv3XEm3Ae3NZn/7qy2KGAt3dexAgSVT+Hc97JKSZnqezgMlv9x6KV0ZkZY7UO5cNLCg==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz", + "integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==", "cpu": [ "x64" ], @@ -3022,13 +2742,29 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz", + "integrity": "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.1.tgz", - "integrity": "sha512-GhRuXlvRE+twf2ES+8REbeCb/zeikNqwD3+6S5y5/x+DYbAQUNl0HNBs4RQJqrechS4v4MruEr8ZtAin/hK5iw==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz", + "integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==", "cpu": [ "x64" ], @@ -3038,13 +2774,13 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.1.tgz", - "integrity": "sha512-ZnWEyCM0G1Ex6JtsygvC3KUUrlDXqOihw8RicRuQAzw+c4f1D66YlPNNV3rkjVW90zXVsHwZYWbJh3v+oQFM9Q==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz", + "integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==", "cpu": [ "x64" ], @@ -3054,13 +2790,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.1.tgz", - "integrity": "sha512-QZ6gXue0vVQY2Oon9WyLFCdSuYbXSoxaZrPuJ4c20j6ICedfsDilNPYfHLlMH7vGfU5DQR0czHLmJvH4Nzis/A==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz", + "integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==", "cpu": [ "arm64" ], @@ -3070,13 +2806,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.1.tgz", - "integrity": "sha512-HzcJa1NcSWTAU0MJIxOho8JftNp9YALui3o+Ny7hCh0v5f90nprly1U3Sj1Ldj/CvKKdvvFsCRvDkpsEMp4DNw==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz", + "integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==", "cpu": [ "ia32" ], @@ -3086,13 +2822,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.1.tgz", - "integrity": "sha512-0MBh53o6XtI6ctDnRMeQ+xoCN8kD2qI1rY1KgF/xdWQwoFeKou7puvDfV8/Wv4Ctx2rRpET/gGdz3YlNtNACSA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz", + "integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==", "cpu": [ "x64" ], @@ -3102,7 +2838,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -3121,9 +2857,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -3168,12 +2904,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3199,18 +2929,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -3242,21 +2960,22 @@ } }, "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", + "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" }, @@ -3303,1000 +3022,653 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "node_modules/@inquirer/checkbox": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-2.5.0.tgz", + "integrity": "sha512-sMgdETOfi2dUHT8r7TT1BTKOwNvdDGFDXYWtQ2J69SvlYNntk9I/gJe7r5yvMwwsuKnYbuRs3pNhx4tgNck5aA==", "dev": true, "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + "@inquirer/core": "^9.1.0", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.3", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" }, "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "node_modules/@inquirer/confirm": { + "version": "3.1.22", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.1.22.tgz", + "integrity": "sha512-gsAKIOWBm2Q87CDfs9fEo7wJT3fwWIJfnDGMn9Qy74gBnNFOACDNfhUzovubbJjWnKLGBln7/NcSmZwj5DuEXg==", "dev": true, - "engines": { - "node": ">=12" + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=18" } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/@inquirer/core": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.2.1.tgz", + "integrity": "sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==", "dev": true, "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "@types/mute-stream": "^0.0.4", + "@types/node": "^22.5.5", + "@types/wrap-ansi": "^3.0.0", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^1.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "node_modules/@inquirer/core/node_modules/@inquirer/type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-2.0.0.tgz", + "integrity": "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==", "dev": true, "dependencies": { - "ansi-regex": "^6.0.1" + "mute-stream": "^1.0.0" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">=18" } }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "node_modules/@inquirer/editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-2.2.0.tgz", + "integrity": "sha512-9KHOpJ+dIL5SZli8lJ6xdaYLPPzB8xB9GZItg39MBybzhxA16vxmszmQFrRwbOA918WA2rvu8xhDEg/p6LXKbw==", "dev": true, "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "external-editor": "^3.1.0" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=18" } }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "node_modules/@inquirer/expand": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-2.3.0.tgz", + "integrity": "sha512-qnJsUcOGCSG1e5DTOErmv2BPQqrtT6uzqn1vI/aYGiPKq+FgslGZmtdnXbhuI7IlT7OByDoEEqdnhUnVR2hhLw==", "dev": true, "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "yoctocolors-cjs": "^2.1.2" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "node_modules/@inquirer/figures": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.6.tgz", + "integrity": "sha512-yfZzps3Cso2UbM7WlxKwZQh2Hs6plrbjs1QnzQDZhK2DgyCo6D8AaHps9olkNcUFlcYERMqU3uJSp1gmy3s/qQ==", "dev": true, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "node_modules/@inquirer/input": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-2.3.0.tgz", + "integrity": "sha512-XfnpCStx2xgh1LIRqPXrTNEEByqQWoxsWYzNRSEUxJ5c6EQlhMogJ3vHKu8aXuTacebtaZzMAHwEL0kAflKOBw==", "dev": true, "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "node_modules/@inquirer/number": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-1.1.0.tgz", + "integrity": "sha512-ilUnia/GZUtfSZy3YEErXLJ2Sljo/mf9fiKc08n18DdwdmDbOzRcTv65H1jjDvlsAuvdFXf4Sa/aL7iw/NanVA==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3" }, "engines": { - "node": ">=6.0.0" + "node": ">=18" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@inquirer/password": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-2.2.0.tgz", + "integrity": "sha512-5otqIpgsPYIshqhgtEwSspBQE40etouR8VIxzpJkv9i0dVHIpyhiivbkH9/dGiMLdyamT54YRdGJLfl8TFnLHg==", "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "ansi-escapes": "^4.3.2" + }, "engines": { - "node": ">=6.0.0" + "node": ">=18" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "node_modules/@inquirer/prompts": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-5.3.8.tgz", + "integrity": "sha512-b2BudQY/Si4Y2a0PdZZL6BeJtl8llgeZa7U2j47aaJSCeAl1e4UI7y8a9bSkO3o/ZbZrgT5muy/34JbsjfIWxA==", "dev": true, + "dependencies": { + "@inquirer/checkbox": "^2.4.7", + "@inquirer/confirm": "^3.1.22", + "@inquirer/editor": "^2.1.22", + "@inquirer/expand": "^2.1.22", + "@inquirer/input": "^2.2.9", + "@inquirer/number": "^1.0.10", + "@inquirer/password": "^2.1.22", + "@inquirer/rawlist": "^2.2.4", + "@inquirer/search": "^1.0.7", + "@inquirer/select": "^2.4.7" + }, "engines": { - "node": ">=6.0.0" + "node": ">=18" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "node_modules/@inquirer/rawlist": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-2.3.0.tgz", + "integrity": "sha512-zzfNuINhFF7OLAtGHfhwOW2TlYJyli7lOUoJUXw/uyklcwalV6WRXBXtFIicN8rTRK1XTiPWB4UY+YuW8dsnLQ==", "dev": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "node_modules/@inquirer/search": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-1.1.0.tgz", + "integrity": "sha512-h+/5LSj51dx7hp5xOn4QFnUaKeARwUCLs6mIhtkJ0JYPBLmEYjdHSYh7I6GrLg9LwpJ3xeX0FZgAG1q0QdCpVQ==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@inquirer/core": "^9.1.0", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.3", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" } }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", - "dev": true - }, - "node_modules/@ljharb/through": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz", - "integrity": "sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==", + "node_modules/@inquirer/select": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-2.5.0.tgz", + "integrity": "sha512-YmDobTItPP3WcEI86GvPo+T2sRHkxxOq/kXmsBjHS5BVXUgvgZ5AfJjkvQvZr03T81NnI3KrrRuMzeuYUQRFOA==", "dev": true, "dependencies": { - "call-bind": "^1.0.7" + "@inquirer/core": "^9.1.0", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.3", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" }, "engines": { - "node": ">= 0.4" - } - }, - "node_modules/@material/animation": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/animation/-/animation-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-1GSJaPKef+7HRuV+HusVZHps64cmZuOItDbt40tjJVaikcaZvwmHlcTxRIqzcRoCdt5ZKHh3NoO7GB9Khg4Jnw==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@material/auto-init": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/auto-init/-/auto-init-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-t7ZGpRJ3ec0QDUO0nJu/SMgLW7qcuG2KqIsEYD1Ej8qhI2xpdR2ydSDQOkVEitXmKoGol1oq4nYSBjTlB65GqA==", - "dependencies": { - "@material/base": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/banner": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/banner/-/banner-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-g9wBUZzYBizyBcBQXTIafnRUUPi7efU9gPJfzeGgkynXiccP/vh5XMmH+PBxl5v+4MlP/d4cZ2NUYoAN7UTqSA==", - "dependencies": { - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/button": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/base": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/base/-/base-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-I9KQOKXpLfJkP8MqZyr8wZIzdPHrwPjFvGd9zSK91/vPyE4hzHRJc/0njsh9g8Lm9PRYLbifXX+719uTbHxx+A==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@material/button": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/button/-/button-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-BHB7iyHgRVH+JF16+iscR+Qaic+p7LU1FOLgP8KucRlpF9tTwIxQA6mJwGRi5gUtcG+vyCmzVS+hIQ6DqT/7BA==", - "dependencies": { - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/touch-target": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/card": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/card/-/card-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-kt7y9/IWOtJTr3Z/AoWJT3ZLN7CLlzXhx2udCLP9ootZU2bfGK0lzNwmo80bv/pJfrY9ihQKCtuGTtNxUy+vIw==", - "dependencies": { - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/checkbox": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/checkbox/-/checkbox-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-rURcrL5O1u6hzWR+dNgiQ/n89vk6tdmdP3mZgnxJx61q4I/k1yijKqNJSLrkXH7Rto3bM5NRKMOlgvMvVd7UMQ==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/touch-target": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/chips": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/chips/-/chips-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-AYAivV3GSk/T/nRIpH27sOHFPaSMrE3L0WYbnb5Wa93FgY8a0fbsFYtSH2QmtwnzXveg+B1zGTt7/xIIcynKdQ==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/checkbox": "15.0.0-canary.7f224ddd4.0", - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/touch-target": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "safevalues": "^0.3.4", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/circular-progress": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/circular-progress/-/circular-progress-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-DJrqCKb+LuGtjNvKl8XigvyK02y36GRkfhMUYTcJEi3PrOE00bwXtyj7ilhzEVshQiXg6AHGWXtf5UqwNrx3Ow==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/progress-indicator": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/data-table": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/data-table/-/data-table-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-/2WZsuBIq9z9RWYF5Jo6b7P6u0fwit+29/mN7rmAZ6akqUR54nXyNfoSNiyydMkzPlZZsep5KrSHododDhBZbA==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/checkbox": "15.0.0-canary.7f224ddd4.0", - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/icon-button": "15.0.0-canary.7f224ddd4.0", - "@material/linear-progress": "15.0.0-canary.7f224ddd4.0", - "@material/list": "15.0.0-canary.7f224ddd4.0", - "@material/menu": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/select": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/touch-target": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/density": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/density/-/density-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-o9EXmGKVpiQ6mHhyV3oDDzc78Ow3E7v8dlaOhgaDSXgmqaE8v5sIlLNa/LKSyUga83/fpGk3QViSGXotpQx0jA==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@material/dialog": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/dialog/-/dialog-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-u0XpTlv1JqWC/bQ3DavJ1JguofTelLT2wloj59l3/1b60jv42JQ6Am7jU3I8/SIUB1MKaW7dYocXjDWtWJakLA==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/button": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/icon-button": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/touch-target": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "node": ">=18" } }, - "node_modules/@material/dom": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/dom/-/dom-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-mQ1HT186GPQSkRg5S18i70typ5ZytfjL09R0gJ2Qg5/G+MLCGi7TAjZZSH65tuD/QGOjel4rDdWOTmYbPYV6HA==", + "node_modules/@inquirer/type": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.5.5.tgz", + "integrity": "sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==", + "dev": true, "dependencies": { - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/drawer": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/drawer/-/drawer-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-qyO0W0KBftfH8dlLR0gVAgv7ZHNvU8ae11Ao6zJif/YxcvK4+gph1z8AO4H410YmC2kZiwpSKyxM1iQCCzbb4g==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/list": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" } }, - "node_modules/@material/elevation": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-tV6s4/pUBECedaI36Yj18KmRCk1vfue/JP/5yYRlFNnLMRVISePbZaKkn/BHXVf+26I3W879+XqIGlDVdmOoMA==", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/fab": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/fab/-/fab-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-4h76QrzfZTcPdd+awDPZ4Q0YdSqsXQnS540TPtyXUJ/5G99V6VwGpjMPIxAsW0y+pmI9UkLL/srrMaJec+7r4Q==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/touch-target": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" } }, - "node_modules/@material/feature-targeting": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-SAjtxYh6YlKZriU83diDEQ7jNSP2MnxKsER0TvFeyG1vX/DWsUyYDOIJTOEa9K1N+fgJEBkNK8hY55QhQaspew==", - "dependencies": { - "tslib": "^2.1.0" + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@material/floating-label": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-0KMo5ijjYaEHPiZ2pCVIcbaTS2LycvH9zEhEMKwPPGssBCX7iz5ffYQFk7e5yrQand1r3jnQQgYfHAwtykArnQ==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@material/focus-ring": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/focus-ring/-/focus-ring-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-Jmg1nltq4J6S6A10EGMZnvufrvU3YTi+8R8ZD9lkSbun0Fm2TVdICQt/Auyi6An9zP66oQN6c31eqO6KfIPsDg==", - "dependencies": { - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0" - } + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true }, - "node_modules/@material/form-field": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/form-field/-/form-field-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-fEPWgDQEPJ6WF7hNnIStxucHR9LE4DoDSMqCsGWS2Yu+NLZYLuCEecgR0UqQsl1EQdNRaFh8VH93KuxGd2hiPg==", + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, "dependencies": { - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/icon-button": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/icon-button/-/icon-button-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-DcK7IL4ICY/DW+48YQZZs9g0U1kRaW0Wb0BxhvppDMYziHo/CTpFdle4gjyuTyRxPOdHQz5a97ru48Z9O4muTw==", - "dependencies": { - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/touch-target": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@material/image-list": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/image-list/-/image-list-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-voMjG2p80XbjL1B2lmF65zO5gEgJOVKClLdqh4wbYzYfwY/SR9c8eLvlYG7DLdFaFBl/7gGxD8TvvZ329HUFPw==", + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, "dependencies": { - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@material/layout-grid": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/layout-grid/-/layout-grid-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-veDABLxMn2RmvfnUO2RUmC1OFfWr4cU+MrxKPoDD2hl3l3eDYv5fxws6r5T1JoSyXoaN+oEZpheS0+M9Ure8Pg==", + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, "dependencies": { - "tslib": "^2.1.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@material/line-ripple": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-f60hVJhIU6I3/17Tqqzch1emUKEcfVVgHVqADbU14JD+oEIz429ZX9ksZ3VChoU3+eejFl+jVdZMLE/LrAuwpg==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" } }, - "node_modules/@material/linear-progress": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-pRDEwPQielDiC9Sc5XhCXrGxP8wWOnAO8sQlMebfBYHYqy5hhiIzibezS8CSaW4MFQFyXmCmpmqWlbqGYRmiyg==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/progress-indicator": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/@material/list": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/list/-/list-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-Is0NV91sJlXF5pOebYAtWLF4wU2MJDbYqztML/zQNENkQxDOvEXu3nWNb3YScMIYJJXvARO0Liur5K4yPagS1Q==", - "dependencies": { - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/@material/menu": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/menu/-/menu-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-D11QU1dXqLbh5X1zKlEhS3QWh0b5BPNXlafc5MXfkdJHhOiieb7LC9hMJhbrHtj24FadJ7evaFW/T2ugJbJNnQ==", - "dependencies": { - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/list": "15.0.0-canary.7f224ddd4.0", - "@material/menu-surface": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/@material/menu-surface": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/menu-surface/-/menu-surface-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-7RZHvw0gbwppaAJ/Oh5SWmfAKJ62aw1IMB3+3MRwsb5PLoV666wInYa+zJfE4i7qBeOn904xqT2Nko5hY0ssrg==", + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, - "node_modules/@material/notched-outline": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-Yg2usuKB2DKlKIBISbie9BFsOVuffF71xjbxPbybvqemxqUBd+bD5/t6H1fLE+F8/NCu5JMigho4ewUU+0RCiw==", - "dependencies": { - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/floating-label": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true }, - "node_modules/@material/progress-indicator": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-UPbDjE5CqT+SqTs0mNFG6uFEw7wBlgYmh+noSkQ6ty/EURm8lF125dmi4dv4kW0+octonMXqkGtAoZwLIHKf/w==", + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@material/radio": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/radio/-/radio-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-wR1X0Sr0KmQLu6+YOFKAI84G3L6psqd7Kys5kfb8WKBM36zxO5HQXC5nJm/Y0rdn22ixzsIz2GBo0MNU4V4k1A==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/touch-target": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@material/ripple": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-JqOsWM1f4aGdotP0rh1vZlPZTg6lZgh39FIYHFMfOwfhR+LAikUJ+37ciqZuewgzXB6iiRO6a8aUH6HR5SJYPg==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@material/rtl": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-UVf14qAtmPiaaZjuJtmN36HETyoKWmsZM/qn1L5ciR2URb8O035dFWnz4ZWFMmAYBno/L7JiZaCkPurv2ZNrGA==", + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.0.tgz", + "integrity": "sha512-zlQONA+msXPPwHWZMKFVS78ewFczIll5lXiVPwFPCZUsrOKdxc2AvxU1HoNBmMRhqDZUR9HkC3UOm+6pME6Xsg==", + "dev": true, "dependencies": { - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@material/segmented-button": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/segmented-button/-/segmented-button-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-LCnVRUSAhELTKI/9hSvyvIvQIpPpqF29BV+O9yM4WoNNmNWqTulvuiv7grHZl6Z+kJuxSg4BGbsPxxb9dXozPg==", - "dependencies": { - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/touch-target": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "node_modules/@jsonjoy.com/util": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.3.0.tgz", + "integrity": "sha512-Cebt4Vk7k1xHy87kHY7KSPLT77A7Ev7IfOblyLZhtYEhrdQ6fX4EoLq3xOQ3O/DRMEh2ok5nyC180E+ABS8Wmw==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@material/select": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/select/-/select-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-WioZtQEXRpglum0cMSzSqocnhsGRr+ZIhvKb3FlaNrTaK8H3Y4QA7rVjv3emRtrLOOjaT6/RiIaUMTo9AGzWQQ==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/floating-label": "15.0.0-canary.7f224ddd4.0", - "@material/line-ripple": "15.0.0-canary.7f224ddd4.0", - "@material/list": "15.0.0-canary.7f224ddd4.0", - "@material/menu": "15.0.0-canary.7f224ddd4.0", - "@material/menu-surface": "15.0.0-canary.7f224ddd4.0", - "@material/notched-outline": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true }, - "node_modules/@material/shape": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/shape/-/shape-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-8z8l1W3+cymObunJoRhwFPKZ+FyECfJ4MJykNiaZq7XJFZkV6xNmqAVrrbQj93FtLsECn9g4PjjIomguVn/OEw==", + "node_modules/@listr2/prompt-adapter-inquirer": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-2.0.15.tgz", + "integrity": "sha512-MZrGem/Ujjd4cPTLYDfCZK2iKKeiO/8OX13S6jqxldLs0Prf2aGqVlJ77nMBqMv7fzqgXEgjrNHLXcKR8l9lOg==", + "dev": true, "dependencies": { - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/slider": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/slider/-/slider-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-QU/WSaSWlLKQRqOhJrPgm29wqvvzRusMqwAcrCh1JTrCl+xwJ43q5WLDfjYhubeKtrEEgGu9tekkAiYfMG7EBw==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/snackbar": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/snackbar/-/snackbar-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-sm7EbVKddaXpT/aXAYBdPoN0k8yeg9+dprgBUkrdqGzWJAeCkxb4fv2B3He88YiCtvkTz2KLY4CThPQBSEsMFQ==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/button": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/icon-button": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "@inquirer/type": "^1.5.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@inquirer/prompts": ">= 3 < 6" } }, - "node_modules/@material/switch": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/switch/-/switch-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-lEDJfRvkVyyeHWIBfoxYjJVl+WlEAE2kZ/+6OqB1FW0OV8ftTODZGhHRSzjVBA1/p4FPuhAtKtoK9jTpa4AZjA==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "safevalues": "^0.3.4", - "tslib": "^2.1.0" - } + "node_modules/@lmdb/lmdb-darwin-arm64": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.0.13.tgz", + "integrity": "sha512-uiKPB0Fv6WEEOZjruu9a6wnW/8jrjzlZbxXscMB8kuCJ1k6kHpcBnuvaAWcqhbI7rqX5GKziwWEdD+wi2gNLfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@material/tab": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/tab/-/tab-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-E1xGACImyCLurhnizyOTCgOiVezce4HlBFAI6YhJo/AyVwjN2Dtas4ZLQMvvWWqpyhITNkeYdOchwCC1mrz3AQ==", - "dependencies": { - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } + "node_modules/@lmdb/lmdb-darwin-x64": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.0.13.tgz", + "integrity": "sha512-bEVIIfK5mSQoG1R19qA+fJOvCB+0wVGGnXHT3smchBVahYBdlPn2OsZZKzlHWfb1E+PhLBmYfqB5zQXFP7hJig==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@material/tab-bar": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/tab-bar/-/tab-bar-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-p1Asb2NzrcECvAQU3b2SYrpyJGyJLQWR+nXTYzDKE8WOpLIRCXap2audNqD7fvN/A20UJ1J8U01ptrvCkwJ4eA==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/tab": "15.0.0-canary.7f224ddd4.0", - "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0", - "@material/tab-scroller": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } + "node_modules/@lmdb/lmdb-linux-arm": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.0.13.tgz", + "integrity": "sha512-Yml1KlMzOnXj/tnW7yX8U78iAzTk39aILYvCPbqeewAq1kSzl+w59k/fiVkTBfvDi/oW/5YRxL+Fq+Y1Fr1r2Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@material/tab-indicator": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/tab-indicator/-/tab-indicator-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-h9Td3MPqbs33spcPS7ecByRHraYgU4tNCZpZzZXw31RypjKvISDv/PS5wcA4RmWqNGih78T7xg4QIGsZg4Pk4w==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } + "node_modules/@lmdb/lmdb-linux-arm64": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.0.13.tgz", + "integrity": "sha512-afbVrsMgZ9dUTNUchFpj5VkmJRxvht/u335jUJ7o23YTbNbnpmXif3VKQGCtnjSh+CZaqm6N3CPG8KO3zwyZ1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@material/tab-scroller": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/tab-scroller/-/tab-scroller-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-LFeYNjQpdXecwECd8UaqHYbhscDCwhGln5Yh+3ctvcEgvmDPNjhKn/DL3sWprWvG8NAhP6sHMrsGhQFVdCWtTg==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/tab": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } + "node_modules/@lmdb/lmdb-linux-x64": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.0.13.tgz", + "integrity": "sha512-vOtxu0xC0SLdQ2WRXg8Qgd8T32ak4SPqk5zjItRszrJk2BdeXqfGxBJbP7o4aOvSPSmSSv46Lr1EP4HXU8v7Kg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@material/textfield": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-AExmFvgE5nNF0UA4l2cSzPghtxSUQeeoyRjFLHLy+oAaE4eKZFrSy0zEpqPeWPQpEMDZk+6Y+6T3cOFYBeSvsw==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/floating-label": "15.0.0-canary.7f224ddd4.0", - "@material/line-ripple": "15.0.0-canary.7f224ddd4.0", - "@material/notched-outline": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } + "node_modules/@lmdb/lmdb-win32-x64": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.0.13.tgz", + "integrity": "sha512-UCrMJQY/gJnOl3XgbWRZZUvGGBuKy6i0YNSptgMzHBjs+QYDYR1Mt/RLTOPy4fzzves65O1EDmlL//OzEqoLlA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@material/theme": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/theme/-/theme-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-hs45hJoE9yVnoVOcsN1jklyOa51U4lzWsEnQEuJTPOk2+0HqCQ0yv/q0InpSnm2i69fNSyZC60+8HADZGF8ugQ==", - "dependencies": { - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@material/tokens": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/tokens/-/tokens-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-r9TDoicmcT7FhUXC4eYMFnt9TZsz0G8T3wXvkKncLppYvZ517gPyD/1+yhuGfGOxAzxTrM66S/oEc1fFE2q4hw==", - "dependencies": { - "@material/elevation": "15.0.0-canary.7f224ddd4.0" - } + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@material/tooltip": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/tooltip/-/tooltip-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-8qNk3pmPLTnam3XYC1sZuplQXW9xLn4Z4MI3D+U17Q7pfNZfoOugGr+d2cLA9yWAEjVJYB0mj8Yu86+udo4N9w==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/button": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "safevalues": "^0.3.4", - "tslib": "^2.1.0" - } + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@material/top-app-bar": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/top-app-bar/-/top-app-bar-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-SARR5/ClYT4CLe9qAXakbr0i0cMY0V3V4pe3ElIJPfL2Z2c4wGR1mTR8m2LxU1MfGKK8aRoUdtfKaxWejp+eNA==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@material/touch-target": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/touch-target/-/touch-target-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-BJo/wFKHPYLGsRaIpd7vsQwKr02LtO2e89Psv0on/p0OephlNIgeB9dD9W+bQmaeZsZ6liKSKRl6wJWDiK71PA==", - "dependencies": { - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@material/typography": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/typography/-/typography-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-kBaZeCGD50iq1DeRRH5OM5Jl7Gdk+/NOfKArkY4ksBZvJiStJ7ACAhpvb8MEGm4s3jvDInQFLsDq3hL+SA79sQ==", - "dependencies": { - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] }, "node_modules/@ngrx/component-store": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@ngrx/component-store/-/component-store-17.2.0.tgz", - "integrity": "sha512-ywhyoZpkbVIY1t5zf7xfWLGkY0A/fQdMjPehHloDI6bRLrmbllBhQRazwZ+FAGIi2myx1+mGcmAc6FbtIikedA==", + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/@ngrx/component-store/-/component-store-18.0.2.tgz", + "integrity": "sha512-IB7ZKFqjDt4duQbfYqXxAOKf9Si9O1HFodqbNCSgi7gnovK/frf/H429a+lYOyItPcpno3ECom6/1k8pE8fWlg==", "dependencies": { - "@ngrx/operators": "17.0.0-beta.0", + "@ngrx/operators": "18.0.1", "tslib": "^2.0.0" }, "peerDependencies": { - "@angular/core": "^17.0.0", + "@angular/core": "^18.0.0", "rxjs": "^6.5.3 || ^7.5.0" } }, "node_modules/@ngrx/effects": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-17.2.0.tgz", - "integrity": "sha512-tXDJNsuBtbvI/7+vYnkDKKpUvLbopw1U5G6LoPnKNrbTPsPcUGmCqF5Su/ZoRN3BhXjt2j+eoeVdpBkxdxMRgg==", + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-18.0.2.tgz", + "integrity": "sha512-YojXcOD9Lsq4kl2HCjENccyUM/mOlgBdtddsg9j/ojzSUgu3ZuBVKLN3atrL2TJYkbMX1MN0RzafSkL3TPGFIA==", "dependencies": { - "@ngrx/operators": "17.0.0-beta.0", + "@ngrx/operators": "18.0.1", "tslib": "^2.0.0" }, "peerDependencies": { - "@angular/core": "^17.0.0", - "@ngrx/store": "17.2.0", + "@angular/core": "^18.0.0", + "@ngrx/store": "18.0.2", "rxjs": "^6.5.3 || ^7.5.0" } }, "node_modules/@ngrx/operators": { - "version": "17.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@ngrx/operators/-/operators-17.0.0-beta.0.tgz", - "integrity": "sha512-EbO8AONuQ6zo2v/mPyBOi4y0CTAp1x4Z+bx7ZF+Pd8BL5ma53BTCL1TmzaeK5zPUe0yApudLk9/ZbHXPnVox5A==", + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@ngrx/operators/-/operators-18.0.1.tgz", + "integrity": "sha512-M+QMrHNKgcuiLaRGZxJ4aQi5/OCRfKC4+T/63dsHyLFZ53/FFpF6a/ytSO1Q+tzOplZ5o99S+i8FVaZqNQ3LmQ==", "dependencies": { "tslib": "^2.3.0" }, @@ -4305,30 +3677,30 @@ } }, "node_modules/@ngrx/store": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-17.2.0.tgz", - "integrity": "sha512-7wKgZ59B/6yQSvvsU0DQXipDqpkAXv7LwcXLD5Ww7nvqN0fQoRPThMh4+Wv55DCJhE0bQc1NEMciLA47uRt7Wg==", + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-18.0.2.tgz", + "integrity": "sha512-ajwv0+njsO4vzArp9esnFvs1wyUb1U1W8E8LSCKrcW2hWWo9o1Pezj+JRsdQwatxHfrrPFuTDyajsl6GQM/JSA==", "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { - "@angular/core": "^17.0.0", + "@angular/core": "^18.0.0", "rxjs": "^6.5.3 || ^7.5.0" } }, "node_modules/@ngtools/webpack": { - "version": "17.3.6", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-17.3.6.tgz", - "integrity": "sha512-equxbgh2DKzZtiFMoVf1KD4yJcH1q8lpqQ/GSPPQUvONcmHrr+yqdRUdaJ7oZCyCYmXF/nByBxtMKtJr6nKZVg==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.6.tgz", + "integrity": "sha512-7HwOPE1EOgcHnpt4brSiT8G2CcXB50G0+CbCBaKGy4LYCG3Y3mrlzF5Fup9HvMJ6Tzqd62RqzpKKYBiGUT7hxg==", "dev": true, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, "peerDependencies": { - "@angular/compiler-cli": "^17.0.0", - "typescript": ">=5.2 <5.5", + "@angular/compiler-cli": "^18.0.0", + "typescript": ">=5.4 <5.6", "webpack": "^5.54.0" } }, @@ -4384,18 +3756,15 @@ } }, "node_modules/@npmcli/agent/node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, "node_modules/@npmcli/fs": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz", - "integrity": "sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", + "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", "dev": true, "dependencies": { "semver": "^7.3.5" @@ -4405,12 +3774,13 @@ } }, "node_modules/@npmcli/git": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.7.tgz", - "integrity": "sha512-WaOVvto604d5IpdCRV2KjQu8PzkfE96d50CQGKgywXh2GxXmDeUO5EWcBC4V57uFyrNqx83+MewuJh3WTR3xPA==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", + "integrity": "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==", "dev": true, "dependencies": { "@npmcli/promise-spawn": "^7.0.0", + "ini": "^4.1.3", "lru-cache": "^10.0.1", "npm-pick-manifest": "^9.0.0", "proc-log": "^4.0.0", @@ -4433,22 +3803,10 @@ } }, "node_modules/@npmcli/git/node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } - }, - "node_modules/@npmcli/git/node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, "node_modules/@npmcli/git/node_modules/which": { "version": "4.0.0", @@ -4491,9 +3849,9 @@ } }, "node_modules/@npmcli/package-json": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.1.0.tgz", - "integrity": "sha512-1aL4TuVrLS9sf8quCLerU3H9J4vtCtgu8VauYozrmEyU57i/EdKleCnsQ7vpnABIH6c9mnTxcH5sFkO3BlV8wQ==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.2.1.tgz", + "integrity": "sha512-f7zYC6kQautXHvNbLEWgD/uGu1+xCn9izgqBfgItWSx22U0ZDekxN08A1vM8cTxj/cRVe0Q94Ode+tdoYmIOOQ==", "dev": true, "dependencies": { "@npmcli/git": "^5.0.0", @@ -4509,36 +3867,25 @@ } }, "node_modules/@npmcli/package-json/node_modules/glob": { - "version": "10.3.12", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", - "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.10.2" + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@npmcli/package-json/node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/@npmcli/promise-spawn": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", @@ -4562,299 +3909,67 @@ }, "node_modules/@npmcli/promise-spawn/node_modules/which": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", - "dev": true, - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/redact": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-1.1.0.tgz", - "integrity": "sha512-PfnWuOkQgu7gCbnSsAisaX7hKOdZ4wSAhAzH3/ph5dSGau52kCRrMMGbiSQLwyTZpgldkZ49b0brkOr1AzGBHQ==", - "dev": true, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/run-script": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-7.0.4.tgz", - "integrity": "sha512-9ApYM/3+rBt9V80aYg6tZfzj3UWdiYyCt7gJUD1VJKvWF5nwKDSICXbYIQbspFTq6TOpbsEtIC0LArB8d9PFmg==", - "dev": true, - "dependencies": { - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/package-json": "^5.0.0", - "@npmcli/promise-spawn": "^7.0.0", - "node-gyp": "^10.0.0", - "which": "^4.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/run-script/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "dev": true, - "engines": { - "node": ">=16" - } - }, - "node_modules/@npmcli/run-script/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", - "dev": true, - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^16.13.0 || >=18.0.0" - } - }, - "node_modules/@nrwl/devkit": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-17.2.8.tgz", - "integrity": "sha512-l2dFy5LkWqSA45s6pee6CoqJeluH+sjRdVnAAQfjLHRNSx6mFAKblyzq5h1f4P0EUCVVVqLs+kVqmNx5zxYqvw==", - "dev": true, - "dependencies": { - "@nx/devkit": "17.2.8" - } - }, - "node_modules/@nrwl/tao": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nrwl/tao/-/tao-17.2.8.tgz", - "integrity": "sha512-Qpk5YKeJ+LppPL/wtoDyNGbJs2MsTi6qyX/RdRrEc8lc4bk6Cw3Oul1qTXCI6jT0KzTz+dZtd0zYD/G7okkzvg==", - "dev": true, - "dependencies": { - "nx": "17.2.8", - "tslib": "^2.3.0" - }, - "bin": { - "tao": "index.js" - } - }, - "node_modules/@nx/devkit": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-17.2.8.tgz", - "integrity": "sha512-6LtiQihtZwqz4hSrtT5cCG5XMCWppG6/B8c1kNksg97JuomELlWyUyVF+sxmeERkcLYFaKPTZytP0L3dmCFXaw==", - "dev": true, - "dependencies": { - "@nrwl/devkit": "17.2.8", - "ejs": "^3.1.7", - "enquirer": "~2.3.6", - "ignore": "^5.0.4", - "semver": "7.5.3", - "tmp": "~0.2.1", - "tslib": "^2.3.0" - }, - "peerDependencies": { - "nx": ">= 16 <= 18" - } - }, - "node_modules/@nx/devkit/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@nx/devkit/node_modules/semver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", - "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@nx/devkit/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/@nx/nx-darwin-arm64": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-17.2.8.tgz", - "integrity": "sha512-dMb0uxug4hM7tusISAU1TfkDK3ixYmzc1zhHSZwpR7yKJIyKLtUpBTbryt8nyso37AS1yH+dmfh2Fj2WxfBHTg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nx/nx-darwin-x64": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-17.2.8.tgz", - "integrity": "sha512-0cXzp1tGr7/6lJel102QiLA4NkaLCkQJj6VzwbwuvmuCDxPbpmbz7HC1tUteijKBtOcdXit1/MEoEU007To8Bw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nx/nx-freebsd-x64": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-17.2.8.tgz", - "integrity": "sha512-YFMgx5Qpp2btCgvaniDGdu7Ctj56bfFvbbaHQWmOeBPK1krNDp2mqp8HK6ZKOfEuDJGOYAp7HDtCLvdZKvJxzA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nx/nx-linux-arm-gnueabihf": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-17.2.8.tgz", - "integrity": "sha512-iN2my6MrhLRkVDtdivQHugK8YmR7URo1wU9UDuHQ55z3tEcny7LV3W9NSsY9UYPK/FrxdDfevj0r2hgSSdhnzA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nx/nx-linux-arm64-gnu": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-17.2.8.tgz", - "integrity": "sha512-Iy8BjoW6mOKrSMiTGujUcNdv+xSM1DALTH6y3iLvNDkGbjGK1Re6QNnJAzqcXyDpv32Q4Fc57PmuexyysZxIGg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nx/nx-linux-arm64-musl": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-17.2.8.tgz", - "integrity": "sha512-9wkAxWzknjpzdofL1xjtU6qPFF1PHlvKCZI3hgEYJDo4mQiatGI+7Ttko+lx/ZMP6v4+Umjtgq7+qWrApeKamQ==", - "cpu": [ - "arm64" - ], + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, "engines": { - "node": ">= 10" + "node": "^16.13.0 || >=18.0.0" } }, - "node_modules/@nx/nx-linux-x64-gnu": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-17.2.8.tgz", - "integrity": "sha512-sjG1bwGsjLxToasZ3lShildFsF0eyeGu+pOQZIp9+gjFbeIkd19cTlCnHrOV9hoF364GuKSXQyUlwtFYFR4VTQ==", - "cpu": [ - "x64" - ], + "node_modules/@npmcli/redact": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-2.0.1.tgz", + "integrity": "sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw==", "dev": true, - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">= 10" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@nx/nx-linux-x64-musl": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-17.2.8.tgz", - "integrity": "sha512-QiakXZ1xBCIptmkGEouLHQbcM4klQkcr+kEaz2PlNwy/sW3gH1b/1c0Ed5J1AN9xgQxWspriAONpScYBRgxdhA==", - "cpu": [ - "x64" - ], + "node_modules/@npmcli/run-script": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-8.1.0.tgz", + "integrity": "sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^10.0.0", + "proc-log": "^4.0.0", + "which": "^4.0.0" + }, "engines": { - "node": ">= 10" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@nx/nx-win32-arm64-msvc": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-17.2.8.tgz", - "integrity": "sha512-XBWUY/F/GU3vKN9CAxeI15gM4kr3GOBqnzFZzoZC4qJt2hKSSUEWsMgeZtsMgeqEClbi4ZyCCkY7YJgU32WUGA==", - "cpu": [ - "arm64" - ], + "node_modules/@npmcli/run-script/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": ">= 10" + "node": ">=16" } }, - "node_modules/@nx/nx-win32-x64-msvc": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-17.2.8.tgz", - "integrity": "sha512-HTqDv+JThlLzbcEm/3f+LbS5/wYQWzb5YDXbP1wi7nlCTihNZOLNqGOkEmwlrR5tAdNHPRpHSmkYg4305W0CtA==", - "cpu": [ - "x64" - ], + "node_modules/@npmcli/run-script/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, "engines": { - "node": ">= 10" + "node": "^16.13.0 || >=18.0.0" } }, "node_modules/@pkgjs/parseargs": { @@ -4880,9 +3995,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz", - "integrity": "sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", + "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", "cpu": [ "arm" ], @@ -4893,9 +4008,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz", - "integrity": "sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", + "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", "cpu": [ "arm64" ], @@ -4906,9 +4021,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz", - "integrity": "sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", + "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", "cpu": [ "arm64" ], @@ -4919,9 +4034,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz", - "integrity": "sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", + "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", "cpu": [ "x64" ], @@ -4932,9 +4047,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz", - "integrity": "sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", + "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", "cpu": [ "arm" ], @@ -4945,9 +4060,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz", - "integrity": "sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", + "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", "cpu": [ "arm" ], @@ -4958,9 +4073,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz", - "integrity": "sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", + "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", "cpu": [ "arm64" ], @@ -4971,9 +4086,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz", - "integrity": "sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", + "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", "cpu": [ "arm64" ], @@ -4984,9 +4099,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz", - "integrity": "sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", + "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", "cpu": [ "ppc64" ], @@ -4997,9 +4112,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz", - "integrity": "sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", + "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", "cpu": [ "riscv64" ], @@ -5010,9 +4125,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz", - "integrity": "sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", + "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", "cpu": [ "s390x" ], @@ -5023,9 +4138,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz", - "integrity": "sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", + "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", "cpu": [ "x64" ], @@ -5036,9 +4151,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz", - "integrity": "sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", + "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", "cpu": [ "x64" ], @@ -5049,9 +4164,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz", - "integrity": "sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", + "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", "cpu": [ "arm64" ], @@ -5062,9 +4177,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz", - "integrity": "sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", + "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", "cpu": [ "ia32" ], @@ -5075,9 +4190,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz", - "integrity": "sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", + "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", "cpu": [ "x64" ], @@ -5088,73 +4203,28 @@ ] }, "node_modules/@schematics/angular": { - "version": "17.0.10", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-17.0.10.tgz", - "integrity": "sha512-rRBlDMXfVPkW3CqVQxazFqkuJXd0BFnD1zjI9WtDiNt3o2pTHbLzuWJnXKuIt5rzv0x/bFwNqIt4CPW2DYGNMg==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "17.0.10", - "@angular-devkit/schematics": "17.0.10", - "jsonc-parser": "3.2.0" - }, - "engines": { - "node": "^18.13.0 || >=20.9.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { - "version": "17.0.10", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.10.tgz", - "integrity": "sha512-93N6oHnmtRt0hL3AXxvnk47sN1rHndfj+pqI5haEY41AGWzIWv9cSBsqlM0PWltNpo6VivcExZESvbLJ71wqbQ==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.6.tgz", + "integrity": "sha512-Y988EoOEQDLEyHu3414T6AeVUyx21AexBHQNbUNQkK8cxlxyB6m1eH1cx6vFgLRFUTsLVv+C6Ln/ICNTfLcG4A==", "dev": true, "dependencies": { - "ajv": "8.12.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.0", - "picomatch": "3.0.1", - "rxjs": "7.8.1", - "source-map": "0.7.4" + "@angular-devkit/core": "18.2.6", + "@angular-devkit/schematics": "18.2.6", + "jsonc-parser": "3.3.1" }, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@schematics/angular/node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, - "node_modules/@schematics/angular/node_modules/picomatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", - "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/@sigstore/bundle": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.1.tgz", - "integrity": "sha512-eqV17lO3EIFqCWK3969Rz+J8MYrRZKw9IBHpSo6DEcEX2c+uzDFOgHE9f2MnyDpfs48LFO4hXmk9KhQ74JzU1g==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", + "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", "dev": true, "dependencies": { - "@sigstore/protobuf-specs": "^0.3.1" + "@sigstore/protobuf-specs": "^0.3.2" }, "engines": { "node": "^16.14.0 || >=18.0.0" @@ -5170,61 +4240,69 @@ } }, "node_modules/@sigstore/protobuf-specs": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.1.tgz", - "integrity": "sha512-aIL8Z9NsMr3C64jyQzE0XlkEyBLpgEJJFDHLVVStkFV5Q3Il/r/YtY6NJWKQ4cy4AE7spP1IX5Jq7VCAxHHMfQ==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.2.tgz", + "integrity": "sha512-c6B0ehIWxMI8wiS/bj6rHMPqeFvngFV7cDU/MY+B16P9Z3Mp9k8L93eYZ7BYzSickzuqAQqAq0V956b3Ju6mLw==", "dev": true, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@sigstore/sign": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.0.tgz", - "integrity": "sha512-tsAyV6FC3R3pHmKS880IXcDJuiFJiKITO1jxR1qbplcsBkZLBmjrEw5GbC7ikD6f5RU1hr7WnmxB/2kKc1qUWQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.2.tgz", + "integrity": "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==", "dev": true, "dependencies": { - "@sigstore/bundle": "^2.3.0", + "@sigstore/bundle": "^2.3.2", "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.1", - "make-fetch-happen": "^13.0.0" + "@sigstore/protobuf-specs": "^0.3.2", + "make-fetch-happen": "^13.0.1", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@sigstore/tuf": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.2.tgz", - "integrity": "sha512-mwbY1VrEGU4CO55t+Kl6I7WZzIl+ysSzEYdA1Nv/FTrl2bkeaPXo5PnWZAVfcY2zSdhOpsUTJW67/M2zHXGn5w==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.4.tgz", + "integrity": "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==", "dev": true, "dependencies": { - "@sigstore/protobuf-specs": "^0.3.0", - "tuf-js": "^2.2.0" + "@sigstore/protobuf-specs": "^0.3.2", + "tuf-js": "^2.2.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@sigstore/verify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.0.tgz", - "integrity": "sha512-hQF60nc9yab+Csi4AyoAmilGNfpXT+EXdBgFkP9OgPwIBPwyqVf7JAWPtmqrrrneTmAT6ojv7OlH1f6Ix5BG4Q==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.1.tgz", + "integrity": "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==", "dev": true, "dependencies": { - "@sigstore/bundle": "^2.3.1", + "@sigstore/bundle": "^2.3.2", "@sigstore/core": "^1.1.0", - "@sigstore/protobuf-specs": "^0.3.1" + "@sigstore/protobuf-specs": "^0.3.2" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/@socket.io/component-emitter": { "version": "3.1.2", @@ -5254,21 +4332,6 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@tufjs/models/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -5322,25 +4385,11 @@ "@types/node": "*" } }, - "node_modules/@types/eslint": { - "version": "8.56.10", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", - "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } + "node_modules/@types/eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", + "dev": true }, "node_modules/@types/estree": { "version": "1.0.5", @@ -5361,9 +4410,21 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz", - "integrity": "sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.0.tgz", + "integrity": "sha512-AbXMTZGt40T+KON9/Fdxx0B2WK5hsgxcfXJLr5bFpZ7b4JCex2WyQPTEKdXqfHiY5nKKBScZ7yCoO6Pvgxfvnw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", "dev": true, "dependencies": { "@types/node": "*", @@ -5379,9 +4440,9 @@ "dev": true }, "node_modules/@types/http-proxy": { - "version": "1.17.14", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", - "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", + "version": "1.17.15", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.15.tgz", + "integrity": "sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==", "dev": true, "dependencies": { "@types/node": "*" @@ -5405,13 +4466,22 @@ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true }, + "node_modules/@types/mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { - "version": "20.12.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.10.tgz", - "integrity": "sha512-Eem5pH9pmWBHoGAT8Dr5fdc5rYA+4NAovdM4EktRPVAAiJhmWWfQrA0cFhAbOsQdSfIHjAud6YdkbL69+zSKjw==", + "version": "22.7.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz", + "integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==", "dev": true, "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, "node_modules/@types/node-forge": { @@ -5424,9 +4494,9 @@ } }, "node_modules/@types/qs": { - "version": "6.9.15", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", - "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", + "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==", "dev": true }, "node_modules/@types/range-parser": { @@ -5436,15 +4506,9 @@ "dev": true }, "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "dev": true - }, - "node_modules/@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", - "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", "dev": true }, "node_modules/@types/send": { @@ -5486,43 +4550,47 @@ "@types/node": "*" } }, + "node_modules/@types/wrap-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", + "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", + "dev": true + }, "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", - "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.0.tgz", + "integrity": "sha512-wORFWjU30B2WJ/aXBfOm1LX9v9nyt9D3jsSOxC3cCaTQGCW5k4jNpmjFv3U7p/7s4yvdjHzwtv2Sd2dOyhjS0A==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/type-utils": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.8.0", + "@typescript-eslint/type-utils": "8.8.0", + "@typescript-eslint/utils": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -5530,301 +4598,216 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", - "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "node_modules/@typescript-eslint/experimental-utils": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz", + "integrity": "sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "@types/json-schema": "^7.0.3", + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/typescript-estree": "3.10.1", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^10.12.0 || >=12.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "*" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/types": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", + "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==", "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "semver": "^7.5.4" - }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" } }, - "node_modules/@typescript-eslint/parser": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", - "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", + "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4" + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/visitor-keys": "3.10.1", + "debug": "^4.1.1", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^10.12.0 || >=12.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, "peerDependenciesMeta": { "typescript": { "optional": true } } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.18.0.tgz", - "integrity": "sha512-ZeMtrXnGmTcHciJN1+u2CigWEEXgy1ufoxtWcHORt5kGvpjjIlK9MUhzHm4RM8iVy6dqSaZA/6PVkX6+r+ChjQ==", + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", + "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.18.0", - "@typescript-eslint/utils": "6.18.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint-visitor-keys": "^1.1.0" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.0.tgz", - "integrity": "sha512-/RFVIccwkwSdW/1zeMx3hADShWbgBxBnV/qSrex6607isYjj05t36P6LyONgqdUrNLl5TYU8NIKdHUYpFvExkA==", - "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.0.tgz", - "integrity": "sha512-klNvl+Ql4NsBNGB4W9TZ2Od03lm7aGvTbs0wYaFYsplVPhr+oeXjlPZCDI4U9jgJIDK38W1FKhacCFzCC+nbIg==", + "node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.18.0", - "@typescript-eslint/visitor-keys": "6.18.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=8.0.0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.0.tgz", - "integrity": "sha512-1wetAlSZpewRDb2h9p/Q8kRjdGuqdTAQbkJIOUMLug2LBLG+QOjiWoSj6/3B/hA9/tVTFFdtiKvAYoYnSRW/RA==", + "node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.18.0", - "eslint-visitor-keys": "^3.4.1" - }, "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=4" } }, - "node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "node_modules/@typescript-eslint/experimental-utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=4.0" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "node_modules/@typescript-eslint/parser": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.0.tgz", + "integrity": "sha512-uEFUsgR+tl8GmzmLjRqz+VrDv4eoaMqMXW7ruXfgThaAShO9JTciKpEsB+TvnfFfbg5IpujgMXVV36gOJRLtZg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "@typescript-eslint/scope-manager": "8.8.0", + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/typescript-estree": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0", + "debug": "^4.3.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, "peerDependenciesMeta": { "typescript": { "optional": true } } }, - "node_modules/@typescript-eslint/utils": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.18.0.tgz", - "integrity": "sha512-wiKKCbUeDPGaYEYQh1S580dGxJ/V9HI7K5sbGAVklyf+o5g3O+adnS4UNJajplF4e7z2q0uVBaTdT/yLb4XAVA==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.0.tgz", + "integrity": "sha512-EL8eaGC6gx3jDd8GwEFEV091210U97J0jeEHrAYvIYosmEGet4wJ+g0SYmLu+oRiAwbSA5AVrt6DxLHfdd+bUg==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.18.0", - "@typescript-eslint/types": "6.18.0", - "@typescript-eslint/typescript-estree": "6.18.0", - "semver": "^7.5.4" + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.0.tgz", - "integrity": "sha512-o/UoDT2NgOJ2VfHpfr+KBY2ErWvCySNUIX/X7O9g8Zzt/tXdpfEU43qbNk8LVuWUT2E0ptzTWXh79i74PP0twA==", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.0.tgz", + "integrity": "sha512-IKwJSS7bCqyCeG4NVGxnOP6lLT9Okc3Zj8hLO96bpMkJab+10HIfJbMouLrlpyOr3yrQ1cA413YPFiGd1mW9/Q==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.18.0", - "@typescript-eslint/visitor-keys": "6.18.0" + "@typescript-eslint/typescript-estree": "8.8.0", + "@typescript-eslint/utils": "8.8.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.0.tgz", - "integrity": "sha512-/RFVIccwkwSdW/1zeMx3hADShWbgBxBnV/qSrex6607isYjj05t36P6LyONgqdUrNLl5TYU8NIKdHUYpFvExkA==", + "node_modules/@typescript-eslint/types": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.0.tgz", + "integrity": "sha512-QJwc50hRCgBd/k12sTykOJbESe1RrzmX6COk8Y525C9l7oweZ+1lw9JiU56im7Amm8swlz00DRIlxMYLizr2Vw==", "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.0.tgz", - "integrity": "sha512-klNvl+Ql4NsBNGB4W9TZ2Od03lm7aGvTbs0wYaFYsplVPhr+oeXjlPZCDI4U9jgJIDK38W1FKhacCFzCC+nbIg==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.0.tgz", + "integrity": "sha512-ZaMJwc/0ckLz5DaAZ+pNLmHv8AMVGtfWxZe/x2JVEkD5LnmhWiQMMcYT7IY7gkdJuzJ9P14fRy28lUrlDSWYdw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.18.0", - "@typescript-eslint/visitor-keys": "6.18.0", + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -5836,34 +4819,39 @@ } } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.0.tgz", - "integrity": "sha512-1wetAlSZpewRDb2h9p/Q8kRjdGuqdTAQbkJIOUMLug2LBLG+QOjiWoSj6/3B/hA9/tVTFFdtiKvAYoYnSRW/RA==", + "node_modules/@typescript-eslint/utils": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.0.tgz", + "integrity": "sha512-QE2MgfOTem00qrlPgyByaCHay9yb1+9BjnMFnSFkUKQfu7adBXDTnCAivURnuPPAG/qiB+kzKkZKmKfaMT0zVg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.18.0", - "eslint-visitor-keys": "^3.4.1" + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.8.0", + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/typescript-estree": "8.8.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.0.tgz", + "integrity": "sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "8.8.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -6052,37 +5040,6 @@ "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", "dev": true }, - "node_modules/@yarnpkg/parsers": { - "version": "3.0.0-rc.46", - "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.0-rc.46.tgz", - "integrity": "sha512-aiATs7pSutzda/rq8fnuPwTglyVwjM22bNnK2ZgjrpAjQHSSl3lztd2f9evst1W/qnC58DRz7T7QndUDumAR4Q==", - "dev": true, - "dependencies": { - "js-yaml": "^3.10.0", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=14.15.0" - } - }, - "node_modules/@zkochan/js-yaml": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.6.tgz", - "integrity": "sha512-nzvgl3VfhcELQ8LyVrYOru+UtAy1nrygk2+AGbTm8a5YcO6o8lSjAT+pfg3vJWxIoZKOUhrK6UU7xW/+00kQrg==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@zkochan/js-yaml/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, "node_modules/abbrev": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", @@ -6106,9 +5063,9 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -6117,10 +5074,10 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "dev": true, "peerDependencies": { "acorn": "^8" @@ -6188,15 +5145,15 @@ } }, "node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "dependencies": { - "fast-deep-equal": "^3.1.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -6204,9 +5161,9 @@ } }, "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "dev": true, "dependencies": { "ajv": "^8.0.0" @@ -6269,12 +5226,12 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", "dev": true, "engines": { - "node": ">=8" + "node": ">=4" } }, "node_modules/ansi-styles": { @@ -6315,13 +5272,10 @@ } }, "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "node_modules/aria-query": { "version": "5.3.0", @@ -6338,31 +5292,19 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, "engines": { "node": ">=8" } }, - "node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, "node_modules/autoprefixer": { - "version": "10.4.18", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz", - "integrity": "sha512-1DKbDfsr6KUElM6wg+0zRNkB/Q7WcKYAaK+pzXn+Xqmszm/5Xa9coeNdtP88Vi+dPzZnMjhge8GIV49ZQkDa+g==", + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", "dev": true, "funding": [ { @@ -6379,11 +5321,11 @@ } ], "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001591", + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", + "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "bin": { @@ -6396,24 +5338,13 @@ "postcss": "^8.1.0" } }, - "node_modules/axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", - "dev": true, - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/axobject-query": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz", - "integrity": "sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", "dev": true, - "dependencies": { - "dequal": "^2.0.3" + "engines": { + "node": ">= 0.4" } }, "node_modules/babel-loader": { @@ -6433,22 +5364,6 @@ "webpack": ">=5" } }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.4.11", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", @@ -6473,57 +5388,25 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.9.0.tgz", - "integrity": "sha512-7nZPG1uzK2Ymhy/NbaOWTg3uibM2BmGASS4vHS4szRZAIR8R6GwA/xAujpdrXU5iyklrimWnLWU+BLF9suPTqg==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.5.0", - "core-js-compat": "^3.34.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz", - "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==", + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.5.tgz", - "integrity": "sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.5.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator/node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz", - "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "@babel/helper-define-polyfill-provider": "^0.6.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -6600,9 +5483,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dev": true, "dependencies": { "bytes": "3.1.2", @@ -6613,7 +5496,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -6676,9 +5559,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", + "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", "dev": true, "funding": [ { @@ -6695,10 +5578,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001663", + "electron-to-chromium": "^1.5.28", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -6735,6 +5618,21 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -6745,9 +5643,9 @@ } }, "node_modules/cacache": { - "version": "18.0.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.3.tgz", - "integrity": "sha512-qXCd4rh6I07cnDqh8V48/94Tc/WSfj+o3Gn6NZ0aZovS255bUx8O13uKxRFd2eWG0xgsco7+YItQNPaa5E85hg==", + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", + "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", "dev": true, "dependencies": { "@npmcli/fs": "^3.1.0", @@ -6768,35 +5666,30 @@ } }, "node_modules/cacache/node_modules/glob": { - "version": "10.3.12", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", - "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.10.2" + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/cacache/node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, "node_modules/call-bind": { "version": "1.0.7", @@ -6826,19 +5719,10 @@ "node": ">=6" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/caniuse-lite": { - "version": "1.0.30001616", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001616.tgz", - "integrity": "sha512-RHVYKov7IcdNjVHJFNY/78RdG4oGVjbayxv8u5IO74Wv7Hlq4PnJE6mo/OjFijjVFNy5ijnCt6H3IIo4t+wfEw==", + "version": "1.0.30001664", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz", + "integrity": "sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g==", "dev": true, "funding": [ { @@ -6909,9 +5793,9 @@ } }, "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "dev": true, "engines": { "node": ">=6.0" @@ -6927,21 +5811,24 @@ } }, "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, "dependencies": { - "restore-cursor": "^3.1.0" + "restore-cursor": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cli-spinners": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", - "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "dev": true, "engines": { "node": ">=6" @@ -6950,6 +5837,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-width": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", @@ -7006,6 +5909,35 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cliui/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -7067,18 +5999,6 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -7282,20 +6202,20 @@ } }, "node_modules/copy-webpack-plugin": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", - "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", + "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", "dev": true, "dependencies": { - "fast-glob": "^3.2.11", + "fast-glob": "^3.3.2", "glob-parent": "^6.0.1", - "globby": "^13.1.1", + "globby": "^14.0.0", "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0" + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", @@ -7317,44 +6237,13 @@ "node": ">=10.13.0" } }, - "node_modules/copy-webpack-plugin/node_modules/globby": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", - "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", - "dev": true, - "dependencies": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.3.0", - "ignore": "^5.2.4", - "merge2": "^1.4.1", - "slash": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/copy-webpack-plugin/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/core-js-compat": { - "version": "3.37.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.0.tgz", - "integrity": "sha512-vYq4L+T8aS5UuFg4UwDhc7YNRWVeVZwltad9C/jV3R2LgVOpS9BDr7l/WL6BN0dbV3k1XejPTHqqEzJgsa0frA==", + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", + "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==", "dev": true, "dependencies": { - "browserslist": "^4.23.0" + "browserslist": "^4.23.3" }, "funding": { "type": "opencollective", @@ -7395,39 +6284,21 @@ "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/cosmiconfig/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/cosmiconfig/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" + "url": "https://github.com/sponsors/d-fischer" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/critters": { - "version": "0.0.22", - "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.22.tgz", - "integrity": "sha512-NU7DEcQZM2Dy8XTKFHxtdnIM/drE312j2T4PCVaSUcS0oBeyT/NImpRw/Ap0zOr/1SE7SgPK9tGPg1WK/sVakw==", + "version": "0.0.24", + "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.24.tgz", + "integrity": "sha512-Oyqew0FGM0wYUSNqR0L6AteO5MpMoUU0rhKRieXeiKs+PmRTxiJMyaunYB2KF6fQ3dzChXKCpbFOEJx3OQ1v/Q==", "dev": true, "dependencies": { "chalk": "^4.1.0", @@ -7524,22 +6395,22 @@ } }, "node_modules/css-loader": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.10.0.tgz", - "integrity": "sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", "dev": true, "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.4", - "postcss-modules-scope": "^3.1.1", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", "semver": "^7.5.4" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", @@ -7547,7 +6418,7 @@ }, "peerDependencies": { "@rspack/core": "0.x || 1.x", - "webpack": "^5.0.0" + "webpack": "^5.27.0" }, "peerDependenciesMeta": { "@rspack/core": { @@ -7614,11 +6485,11 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -7635,6 +6506,34 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/default-gateway": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", @@ -7677,21 +6576,15 @@ } }, "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "dev": true, "engines": { - "node": ">=0.4.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/depd": { @@ -7722,6 +6615,15 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", @@ -7734,27 +6636,6 @@ "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", "dev": true }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -7852,33 +6733,6 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, - "node_modules/dotenv": { - "version": "16.3.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.2.tgz", - "integrity": "sha512-HTlk5nmhkm8F6JcdXvHIzaorzCoziNQT9mGxLPVXW8wJF1TiGSL60ZGB4gHWabHOaMmWmhvk2/lPHfnBiT78AQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" - } - }, - "node_modules/dotenv-expand": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", - "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, "node_modules/duplexify": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", @@ -7902,31 +6756,16 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dev": true, - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/electron-to-chromium": { - "version": "1.4.757", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.757.tgz", - "integrity": "sha512-jftDaCknYSSt/+KKeXzH3LX5E2CvRLm75P3Hj+J/dv3CL0qUYcOt13d5FN1NiL5IJbbhzHrb3BomeG2tkSlZmw==", + "version": "1.5.30", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.30.tgz", + "integrity": "sha512-sXI35EBN4lYxzc/pIGorlymYNzDBOqkSlVRe6MkgBsW/hW1tpC/HDJ2fjG7XnjakzfLEuvdmux0Mjs6jHq4UOA==", "dev": true }, "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", "dev": true }, "node_modules/emojis-list": { @@ -7979,9 +6818,9 @@ } }, "node_modules/engine.io": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", - "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.1.tgz", + "integrity": "sha512-NEpDCw9hrvBW+hVEOK4T7v0jFJ++KgtPl4jKFwsZVfG1XhS0dCrSb3VMb9gPAd7VAdW52VT1EnaNiU2vM8C0og==", "dev": true, "dependencies": { "@types/cookie": "^0.4.1", @@ -8000,18 +6839,39 @@ } }, "node_modules/engine.io-parser": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", - "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", "dev": true, "engines": { "node": ">=10.0.0" } }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/enhanced-resolve": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz", - "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -8022,22 +6882,29 @@ } }, "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, "dependencies": { - "ansi-colors": "^4.1.1" + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8.6" } }, "node_modules/ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", - "dev": true + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.1.tgz", + "integrity": "sha512-QHuXVeZx9d+tIQAz/XztU0ZwZf2Agg9CcXcgE1rurqvdBeDBrpSwjl8/6XUqMg7tw2Y7uAdKb2sRv+bSEFqQ5A==", + "dev": true, + "dependencies": { + "punycode": "^1.4.1" + }, + "engines": { + "node": ">= 0.4" + } }, "node_modules/entities": { "version": "4.5.0", @@ -8060,6 +6927,18 @@ "node": ">=6" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/err-code": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", @@ -8110,66 +6989,66 @@ } }, "node_modules/es-module-lexer": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.2.tgz", - "integrity": "sha512-l60ETUTmLqbVbVHv1J4/qj+M8nq7AwMzEcg3kmJDt9dCNrTk+yHcYFf/Kw75pMDwd9mPcIGCG5LcS20SxYRzFA==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", "dev": true }, "node_modules/esbuild": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.1.tgz", - "integrity": "sha512-OJwEgrpWm/PCMsLVWXKqvcjme3bHNpOgN7Tb6cQnR5n0TPbQx1/Xrn7rqM+wn17bYeT6MGB5sn1Bh5YiGi70nA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", + "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", "dev": true, "hasInstallScript": true, - "optional": true, "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.1", - "@esbuild/android-arm": "0.20.1", - "@esbuild/android-arm64": "0.20.1", - "@esbuild/android-x64": "0.20.1", - "@esbuild/darwin-arm64": "0.20.1", - "@esbuild/darwin-x64": "0.20.1", - "@esbuild/freebsd-arm64": "0.20.1", - "@esbuild/freebsd-x64": "0.20.1", - "@esbuild/linux-arm": "0.20.1", - "@esbuild/linux-arm64": "0.20.1", - "@esbuild/linux-ia32": "0.20.1", - "@esbuild/linux-loong64": "0.20.1", - "@esbuild/linux-mips64el": "0.20.1", - "@esbuild/linux-ppc64": "0.20.1", - "@esbuild/linux-riscv64": "0.20.1", - "@esbuild/linux-s390x": "0.20.1", - "@esbuild/linux-x64": "0.20.1", - "@esbuild/netbsd-x64": "0.20.1", - "@esbuild/openbsd-x64": "0.20.1", - "@esbuild/sunos-x64": "0.20.1", - "@esbuild/win32-arm64": "0.20.1", - "@esbuild/win32-ia32": "0.20.1", - "@esbuild/win32-x64": "0.20.1" + "@esbuild/aix-ppc64": "0.23.0", + "@esbuild/android-arm": "0.23.0", + "@esbuild/android-arm64": "0.23.0", + "@esbuild/android-x64": "0.23.0", + "@esbuild/darwin-arm64": "0.23.0", + "@esbuild/darwin-x64": "0.23.0", + "@esbuild/freebsd-arm64": "0.23.0", + "@esbuild/freebsd-x64": "0.23.0", + "@esbuild/linux-arm": "0.23.0", + "@esbuild/linux-arm64": "0.23.0", + "@esbuild/linux-ia32": "0.23.0", + "@esbuild/linux-loong64": "0.23.0", + "@esbuild/linux-mips64el": "0.23.0", + "@esbuild/linux-ppc64": "0.23.0", + "@esbuild/linux-riscv64": "0.23.0", + "@esbuild/linux-s390x": "0.23.0", + "@esbuild/linux-x64": "0.23.0", + "@esbuild/netbsd-x64": "0.23.0", + "@esbuild/openbsd-arm64": "0.23.0", + "@esbuild/openbsd-x64": "0.23.0", + "@esbuild/sunos-x64": "0.23.0", + "@esbuild/win32-arm64": "0.23.0", + "@esbuild/win32-ia32": "0.23.0", + "@esbuild/win32-x64": "0.23.0" } }, "node_modules/esbuild-wasm": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.20.1.tgz", - "integrity": "sha512-6v/WJubRsjxBbQdz6izgvx7LsVFvVaGmSdwrFHmEzoVgfXL89hkKPoQHsnVI2ngOkcBUQT9kmAM1hVL1k/Av4A==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.23.0.tgz", + "integrity": "sha512-6jP8UmWy6R6TUUV8bMuC3ZyZ6lZKI56x0tkxyCIqWwRRJ/DgeQKneh/Oid5EoGoPFLrGNkz47ZEtWAYuiY/u9g==", "dev": true, "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "engines": { "node": ">=6" @@ -8191,16 +7070,16 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -8258,13 +7137,13 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", - "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", + "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", "dev": true, "dependencies": { "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.6" + "synckit": "^0.9.1" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -8288,9 +7167,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", - "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", + "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -8303,6 +7182,30 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/eslint-visitor-keys": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", @@ -8346,12 +7249,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -8424,22 +7321,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint/node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -8451,104 +7332,47 @@ "engines": { "node": ">=10.13.0" } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { - "yocto-queue": "^0.1.0" + "type-fest": "^0.20.2" }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "dependencies": { - "p-limit": "^3.0.2" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "*" } }, "node_modules/eslint/node_modules/supports-color": { @@ -8606,9 +7430,9 @@ } }, "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -8694,6 +7518,27 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/execa/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, "node_modules/exponential-backoff": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", @@ -8701,37 +7546,37 @@ "dev": true }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dev": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -8760,14 +7605,23 @@ "ms": "2.0.0" } }, + "node_modules/express/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/express/node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dev": true, "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -8813,18 +7667,6 @@ "node": ">=4" } }, - "node_modules/external-editor/node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -8865,6 +7707,12 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-uri": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.2.tgz", + "integrity": "sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row==", + "dev": true + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -8886,21 +7734,6 @@ "node": ">=0.8.0" } }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -8913,27 +7746,6 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -9008,16 +7820,19 @@ } }, "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "dependencies": { - "locate-path": "^5.0.0", + "locate-path": "^6.0.0", "path-exists": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/flat": { @@ -9050,9 +7865,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "dev": true, "funding": [ { @@ -9070,9 +7885,9 @@ } }, "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dev": true, "dependencies": { "cross-spawn": "^7.0.0", @@ -9085,32 +7900,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -9142,24 +7931,18 @@ "node": ">= 0.6" } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, "node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "dependencies": { "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" }, "engines": { - "node": ">=14.14" + "node": ">=6 <7 || >=8" } }, "node_modules/fs-minipass": { @@ -9174,12 +7957,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/fs-monkey": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", - "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==", - "dev": true - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -9208,6 +7985,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -9226,6 +8009,18 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", + "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -9245,15 +8040,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -9270,6 +8056,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -9333,20 +8120,20 @@ } }, "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", + "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", "dev": true, "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -9482,13 +8269,10 @@ } }, "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, "node_modules/hpack.js": { "version": "2.1.6", @@ -9644,33 +8428,26 @@ } }, "node_modules/http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.0.tgz", + "integrity": "sha512-36AV1fIaI2cWRzHo+rbcxhe3M3jUDCNzc4D5zRl57sEWRAxdXYtw7FSQKYY6PDKssiAKjLYypbssHk+xs/kMXw==", "dev": true, "dependencies": { - "@types/http-proxy": "^1.17.8", + "@types/http-proxy": "^1.17.10", + "debug": "^4.3.4", "http-proxy": "^1.18.1", "is-glob": "^4.0.1", "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" + "micromatch": "^4.0.5" }, "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, "dependencies": { "agent-base": "^7.0.2", @@ -9689,6 +8466,15 @@ "node": ">=10.17.0" } }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "engines": { + "node": ">=10.18" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -9733,9 +8519,9 @@ ] }, "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "engines": { "node": ">= 4" @@ -9767,9 +8553,9 @@ } }, "node_modules/immutable": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", - "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", "dev": true }, "node_modules/import-fresh": { @@ -9788,15 +8574,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -9819,6 +8596,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -9830,52 +8608,14 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/inquirer": { - "version": "9.2.15", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.15.tgz", - "integrity": "sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==", - "dev": true, - "dependencies": { - "@ljharb/through": "^2.3.12", - "ansi-escapes": "^4.3.2", - "chalk": "^5.3.0", - "cli-cursor": "^3.1.0", - "cli-width": "^4.1.0", - "external-editor": "^3.1.0", - "figures": "^3.2.0", - "lodash": "^4.17.21", - "mute-stream": "1.0.0", - "ora": "^5.4.1", - "run-async": "^3.0.0", - "rxjs": "^7.8.1", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^6.2.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/inquirer/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/ip-address": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", @@ -9889,12 +8629,6 @@ "node": ">= 12" } }, - "node_modules/ip-address/node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true - }, "node_modules/ipaddr.js": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", @@ -9923,27 +8657,30 @@ } }, "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "dev": true, "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", "dev": true, "bin": { "is-docker": "cli.js" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -9959,12 +8696,15 @@ } }, "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-glob": { @@ -9979,6 +8719,24 @@ "node": ">=0.10.0" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -9994,6 +8752,18 @@ "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", "dev": true }, + "node_modules/is-network-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", + "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -10067,15 +8837,18 @@ "dev": true }, "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", "dev": true, "dependencies": { - "is-docker": "^2.0.0" + "is-inside-container": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/isarray": { @@ -10097,324 +8870,60 @@ } }, "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", - "dev": true, - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jake": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz", - "integrity": "sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==", - "dev": true, - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jake/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jake/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/jake/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jake/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jake/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jake/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jake/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/jake/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jasmine-core": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.0.tgz", - "integrity": "sha512-O236+gd0ZXS8YAjFx8xKaJ94/erqUliEkJTDedyE7iHvv4ZVqi+q+8acJxu05/WJDKm512EUNn809In37nWlAQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=10" } }, - "node_modules/jest-diff/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-diff/node_modules/has-flag": { + "node_modules/istanbul-lib-report/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", @@ -10423,7 +8932,7 @@ "node": ">=8" } }, - "node_modules/jest-diff/node_modules/supports-color": { + "node_modules/istanbul-lib-report/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", @@ -10435,15 +8944,63 @@ "node": ">=8" } }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jasmine-core": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.1.tgz", + "integrity": "sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==", + "dev": true + }, "node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -10483,9 +9040,9 @@ } }, "node_modules/jiti": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", - "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", "dev": true, "bin": { "jiti": "bin/jiti.js" @@ -10507,13 +9064,12 @@ "dev": true }, "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" @@ -10577,19 +9133,16 @@ } }, "node_modules/jsonc-parser": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", - "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", "dev": true }, "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -10604,9 +9157,9 @@ ] }, "node_modules/karma": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.3.tgz", - "integrity": "sha512-LuucC/RE92tJ8mlCwqEoRWXP38UMAqpnq98vktmS9SznSoUPPUJQbc91dHcxcunROvfQjdORVA/YFviH+Xci9Q==", + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", + "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", "dev": true, "dependencies": { "@colors/colors": "1.5.0", @@ -10689,6 +9242,22 @@ "concat-map": "0.0.1" } }, + "node_modules/karma-coverage/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/karma-coverage/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -10701,6 +9270,15 @@ "node": "*" } }, + "node_modules/karma-coverage/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/karma-jasmine": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.1.0.tgz", @@ -10790,6 +9368,21 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/karma/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/karma/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/karma/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -10811,6 +9404,29 @@ "node": ">=0.10.0" } }, + "node_modules/karma/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/karma/node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, + "engines": { + "node": ">=14.14" + } + }, "node_modules/karma/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -10873,19 +9489,10 @@ "node": ">=0.10.0" } }, - "node_modules/klona": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", - "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, "node_modules/launch-editor": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.1.tgz", - "integrity": "sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.9.1.tgz", + "integrity": "sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==", "dev": true, "dependencies": { "picocolors": "^1.0.0", @@ -10919,23 +9526,29 @@ } }, "node_modules/less-loader": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-11.1.0.tgz", - "integrity": "sha512-C+uDBV7kS7W5fJlUjq5mPBeBVhYpTIm5gB09APT9o3n/ILeaXVsiSFTbZpTJCJwQ/Crczfn3DmfQFwxYusWFug==", + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-12.2.0.tgz", + "integrity": "sha512-MYUxjSQSBUQmowc0l5nPieOYwMzGPUaTzB6inNW/bdPEG9zOL3eAAD1Qw5ZxSPk7we5dMojHwNODYMV1hq4EVg==", "dev": true, - "dependencies": { - "klona": "^2.0.4" - }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { + "@rspack/core": "0.x || 1.x", "less": "^3.5.0 || ^4.0.0", "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/less/node_modules/make-dir": { @@ -10990,46 +9603,147 @@ "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", "integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==", "engines": { - "node": ">=0.10.0" + "node": ">=0.10.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/license-webpack-plugin": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", + "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", + "dev": true, + "dependencies": { + "webpack-sources": "^3.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-sources": { + "optional": true + } + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/listr2": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.4.tgz", + "integrity": "sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==", + "dev": true, + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "node_modules/listr2/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">= 0.8.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/license-webpack-plugin": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", - "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", "dev": true, "dependencies": { - "webpack-sources": "^3.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - }, - "webpack-sources": { - "optional": true - } + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/lines-and-columns": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", - "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", + "node_modules/lmdb": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.0.13.tgz", + "integrity": "sha512-UGe+BbaSUQtAMZobTb4nHvFMrmvuAQKSeaqAX2meTEQjfsbpl5sxdHD8T72OnwD4GU9uwNhYXIVe4QGs8N9Zyw==", "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "hasInstallScript": true, + "dependencies": { + "msgpackr": "^1.10.2", + "node-addon-api": "^6.1.0", + "node-gyp-build-optional-packages": "5.2.2", + "ordered-binary": "^1.4.1", + "weak-lru-cache": "^1.2.2" + }, + "bin": { + "download-lmdb-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@lmdb/lmdb-darwin-arm64": "3.0.13", + "@lmdb/lmdb-darwin-x64": "3.0.13", + "@lmdb/lmdb-linux-arm": "3.0.13", + "@lmdb/lmdb-linux-arm64": "3.0.13", + "@lmdb/lmdb-linux-x64": "3.0.13", + "@lmdb/lmdb-win32-x64": "3.0.13" } }, "node_modules/loader-runner": { @@ -11042,24 +9756,27 @@ } }, "node_modules/loader-utils": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", - "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", + "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", "dev": true, "engines": { "node": ">= 12.13.0" } }, "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "dependencies": { - "p-locate": "^4.1.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lodash": { @@ -11080,6 +9797,12 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -11166,6 +9889,127 @@ "node": ">=8" } }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "dev": true, + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/log4js": { "version": "6.9.1", "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", @@ -11183,9 +10027,9 @@ } }, "node_modules/loglevel": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz", - "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", "dev": true, "engines": { "node": ">= 0.6.0" @@ -11270,15 +10114,12 @@ } }, "node_modules/magic-string": { - "version": "0.30.8", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", - "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", "dev": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/make-dir": { @@ -11319,15 +10160,6 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/make-fetch-happen/node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -11338,22 +10170,32 @@ } }, "node_modules/memfs": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", - "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.12.0.tgz", + "integrity": "sha512-74wDsex5tQDSClVkeK1vtxqYCAgCoXxx+K4NSHzgU/muYVYByFqa+0RnrPO9NM6naWm1+G9JmZ0p6QHhXmeYfA==", "dev": true, "dependencies": { - "fs-monkey": "^1.0.4" + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.3.0", + "tree-dump": "^1.0.1", + "tslib": "^2.0.0" }, "engines": { "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "dev": true + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -11380,12 +10222,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -11446,10 +10288,22 @@ "node": ">=6" } }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mini-css-extract-plugin": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.1.tgz", - "integrity": "sha512-/1HDlyFRxWIZPI1ZpgqlZ8jMw/1Dp/dl3P0L1jtZ+zVcHqwPhGwaJwKL00WVgfnBy6PWCde9W65or7IIETImuA==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", + "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", "dev": true, "dependencies": { "schema-utils": "^4.0.0", @@ -11473,9 +10327,9 @@ "dev": true }, "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -11496,9 +10350,9 @@ } }, "node_modules/minipass": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.0.tgz", - "integrity": "sha512-oGZRv2OT1lO2UF1zUcwdTb3wqUwI0kBGTgt/T7OdSj6M6N5m3o5uPf0AIW6lVxGGoiWUR7e2AwTE+xiwK8WQig==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, "engines": { "node": ">=16 || 14 >=14.17" @@ -11563,34 +10417,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/minipass-json-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", - "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", - "dev": true, - "dependencies": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" - } - }, - "node_modules/minipass-json-stream/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-json-stream/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/minipass-pipeline": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", @@ -11755,26 +10581,6 @@ "node": ">=10" } }, - "node_modules/mqtt/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/mqtt/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -11790,9 +10596,40 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/msgpackr": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.0.tgz", + "integrity": "sha512-I8qXuuALqJe5laEBYoFykChhSXLikZmUhccjGsPuSJ/7uPip2TJ7lwdIQwWSAi0jGZDXv4WOP8Qg65QZRuXxXw==", + "dev": true, + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" + } }, "node_modules/multicast-dns": { "version": "7.2.5", @@ -11927,13 +10764,19 @@ "node-gyp-build": "^4.2.2" } }, - "node_modules/node-addon-api": { + "node_modules/nice-napi/node_modules/node-addon-api": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", "dev": true, "optional": true }, + "node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -11944,9 +10787,9 @@ } }, "node_modules/node-gyp": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.1.0.tgz", - "integrity": "sha512-B4J5M1cABxPc5PwfjhbV5hoy2DP9p8lFXASnEN6hugXOa61416tnTZ29x9sSwAd0o99XNIcpvDDy1swAExsVKA==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.2.0.tgz", + "integrity": "sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw==", "dev": true, "dependencies": { "env-paths": "^2.2.0", @@ -11955,9 +10798,9 @@ "graceful-fs": "^4.2.6", "make-fetch-happen": "^13.0.0", "nopt": "^7.0.0", - "proc-log": "^3.0.0", + "proc-log": "^4.1.0", "semver": "^7.3.5", - "tar": "^6.1.2", + "tar": "^6.2.1", "which": "^4.0.0" }, "bin": { @@ -11968,9 +10811,9 @@ } }, "node_modules/node-gyp-build": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz", - "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==", + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.2.tgz", + "integrity": "sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==", "dev": true, "optional": true, "bin": { @@ -11979,24 +10822,36 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "dev": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, "node_modules/node-gyp/node_modules/glob": { - "version": "10.3.12", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", - "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.10.2" + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, "funding": { "url": "https://github.com/sponsors/isaacs" } @@ -12025,16 +10880,10 @@ "node": "^16.13.0 || >=18.0.0" } }, - "node_modules/node-machine-id": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", - "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==", - "dev": true - }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "dev": true }, "node_modules/nopt": { @@ -12053,13 +10902,12 @@ } }, "node_modules/normalize-package-data": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.1.tgz", - "integrity": "sha512-6rvCfeRW+OEZagAB4lMLSNuTNYZWLVtKccK79VSTf//yTY5VOCgcpH80O+bZK8Neps7pUnd5G+QlMg1yV/2iZQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", "dev": true, "dependencies": { "hosted-git-info": "^7.0.0", - "is-core-module": "^2.8.1", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" }, @@ -12073,1204 +10921,1476 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-bundled": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", + "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "dev": true, + "dependencies": { + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-install-checks": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "dev": true, + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-packlist": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", + "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", + "dev": true, + "dependencies": { + "ignore-walk": "^6.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", + "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", + "dev": true, + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-17.1.0.tgz", + "integrity": "sha512-5+bKQRH0J1xG1uZ1zMNvxW0VEyoNWgJpY9UDuluPFLKDfJ9u2JmmjmTJV1srBGQOROfdBMiVvnH2Zvpbm+xkVA==", + "dev": true, + "dependencies": { + "@npmcli/redact": "^2.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^13.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minizlib": "^2.1.2", + "npm-package-arg": "^11.0.0", + "proc-log": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/number-allocator": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz", + "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==", + "dependencies": { + "debug": "^4.3.1", + "js-sdsl": "4.3.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "dev": true, + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "node_modules/ora/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/npm-bundled": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", - "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "npm-normalize-package-bin": "^3.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/npm-install-checks": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", - "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "node_modules/ora/node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, "dependencies": { - "semver": "^7.1.1" + "restore-cursor": "^3.1.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/npm-normalize-package-bin": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", - "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "node_modules/ora/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=7.0.0" } }, - "node_modules/npm-package-arg": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.1.tgz", - "integrity": "sha512-M7s1BD4NxdAvBKUPqqRW957Xwcl/4Zvo8Aj+ANrzvIPzGJZElrH7Z//rSaec2ORcND6FHHLnZeY8qgTpXDMFQQ==", + "node_modules/ora/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/ora/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "dependencies": { - "hosted-git-info": "^7.0.0", - "proc-log": "^3.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" - }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/npm-packlist": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", - "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", + "node_modules/ora/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "dependencies": { - "ignore-walk": "^6.0.4" + "mimic-fn": "^2.1.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm-pick-manifest": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.0.0.tgz", - "integrity": "sha512-VfvRSs/b6n9ol4Qb+bDwNGUXutpy76x6MARw/XssevE0TnctIKcmklJZM5Z7nqs5z5aW+0S63pgCNbpkUNNXBg==", + "node_modules/ora/node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, "dependencies": { - "npm-install-checks": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^11.0.0", - "semver": "^7.3.5" + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/npm-registry-fetch": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-16.2.1.tgz", - "integrity": "sha512-8l+7jxhim55S85fjiDGJ1rZXBWGtRLi1OSb4Z3BPLObPuIaeKRlPRiYMSHU4/81ck3t71Z+UwDDl47gcpmfQQA==", + "node_modules/ora/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/ora/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "@npmcli/redact": "^1.1.0", - "make-fetch-happen": "^13.0.0", - "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", - "minipass-json-stream": "^1.0.1", - "minizlib": "^2.1.2", - "npm-package-arg": "^11.0.0", - "proc-log": "^4.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/npm-registry-fetch/node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "node_modules/ordered-binary": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.2.tgz", + "integrity": "sha512-JTo+4+4Fw7FreyAvlSLjb1BBVaxEQAacmjD3jjuyPZclpbEghTvQZbXBb2qPd2LeIMxiHwXBZUcpmG2Gl/mDEA==", + "dev": true + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", "dev": true, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "dependencies": { - "path-key": "^3.0.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "dependencies": { - "boolbase": "^1.0.0" + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/number-allocator": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz", - "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==", - "dependencies": { - "debug": "^4.3.1", - "js-sdsl": "4.3.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nx": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/nx/-/nx-17.2.8.tgz", - "integrity": "sha512-rM5zXbuXLEuqQqcjVjClyvHwRJwt+NVImR2A6KFNG40Z60HP6X12wAxxeLHF5kXXTDRU0PFhf/yACibrpbPrAw==", + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, - "hasInstallScript": true, "dependencies": { - "@nrwl/tao": "17.2.8", - "@yarnpkg/lockfile": "^1.1.0", - "@yarnpkg/parsers": "3.0.0-rc.46", - "@zkochan/js-yaml": "0.0.6", - "axios": "^1.5.1", - "chalk": "^4.1.0", - "cli-cursor": "3.1.0", - "cli-spinners": "2.6.1", - "cliui": "^8.0.1", - "dotenv": "~16.3.1", - "dotenv-expand": "~10.0.0", - "enquirer": "~2.3.6", - "figures": "3.2.0", - "flat": "^5.0.2", - "fs-extra": "^11.1.0", - "glob": "7.1.4", - "ignore": "^5.0.4", - "jest-diff": "^29.4.1", - "js-yaml": "4.1.0", - "jsonc-parser": "3.2.0", - "lines-and-columns": "~2.0.3", - "minimatch": "3.0.5", - "node-machine-id": "1.1.12", - "npm-run-path": "^4.0.1", - "open": "^8.4.0", - "semver": "7.5.3", - "string-width": "^4.2.3", - "strong-log-transformer": "^2.1.0", - "tar-stream": "~2.2.0", - "tmp": "~0.2.1", - "tsconfig-paths": "^4.1.2", - "tslib": "^2.3.0", - "yargs": "^17.6.2", - "yargs-parser": "21.1.1" - }, - "bin": { - "nx": "bin/nx.js", - "nx-cloud": "bin/nx-cloud.js" - }, - "optionalDependencies": { - "@nx/nx-darwin-arm64": "17.2.8", - "@nx/nx-darwin-x64": "17.2.8", - "@nx/nx-freebsd-x64": "17.2.8", - "@nx/nx-linux-arm-gnueabihf": "17.2.8", - "@nx/nx-linux-arm64-gnu": "17.2.8", - "@nx/nx-linux-arm64-musl": "17.2.8", - "@nx/nx-linux-x64-gnu": "17.2.8", - "@nx/nx-linux-x64-musl": "17.2.8", - "@nx/nx-win32-arm64-msvc": "17.2.8", - "@nx/nx-win32-x64-msvc": "17.2.8" + "aggregate-error": "^3.0.0" }, - "peerDependencies": { - "@swc-node/register": "^1.6.7", - "@swc/core": "^1.3.85" + "engines": { + "node": ">=10" }, - "peerDependenciesMeta": { - "@swc-node/register": { - "optional": true - }, - "@swc/core": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nx/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/p-retry": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.0.tgz", + "integrity": "sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" }, "engines": { - "node": ">=8" + "node": ">=16.17" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nx/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/nx/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/p-retry/node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": ">= 4" } }, - "node_modules/nx/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, + "node_modules/pacote": { + "version": "18.0.6", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-18.0.6.tgz", + "integrity": "sha512-+eK3G27SMwsB8kLIuj4h1FUhHtwiEUo21Tw8wNjmvdlpOEr613edv+8FUsTj/4F/VN5ywGE19X18N7CC2EJk6A==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@npmcli/git": "^5.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/package-json": "^5.1.0", + "@npmcli/promise-spawn": "^7.0.0", + "@npmcli/run-script": "^8.0.0", + "cacache": "^18.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^11.0.0", + "npm-packlist": "^8.0.0", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^17.0.0", + "proc-log": "^4.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^2.2.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "bin/index.js" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/nx/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "callsites": "^3.0.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=6" } }, - "node_modules/nx/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/nx/node_modules/glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" }, "engines": { - "node": "*" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nx/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/parse-json/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", "dev": true, "engines": { - "node": ">=8" + "node": ">= 0.10" } }, - "node_modules/nx/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "devOptional": true, "dependencies": { - "argparse": "^2.0.1" + "entities": "^4.4.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/nx/node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, - "node_modules/nx/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/parse5-html-rewriting-stream": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-7.0.0.tgz", + "integrity": "sha512-mazCyGWkmCRWDI15Zp+UiCqMp/0dgEmkZRvhlsqqKYr4SsVm/TvnSpD9fCvqCA2zoWJcfRym846ejWBBHRiYEg==", "dev": true, "dependencies": { - "yallist": "^4.0.0" + "entities": "^4.3.0", + "parse5": "^7.0.0", + "parse5-sax-parser": "^7.0.0" }, - "engines": { - "node": ">=10" + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/nx/node_modules/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", + "node_modules/parse5-sax-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", + "integrity": "sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "parse5": "^7.0.0" }, - "engines": { - "node": "*" + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/nx/node_modules/semver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", - "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, "engines": { - "node": ">=10" + "node": ">= 0.8" } }, - "node_modules/nx/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { "node": ">=8" } }, - "node_modules/nx/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "engines": { "node": ">=0.10.0" } }, - "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=8" } }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "dependencies": { - "ee-first": "1.1.1" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true, - "engines": { - "node": ">= 0.8" - } + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } + "node_modules/path-to-regexp": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "dev": true }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, "engines": { - "node": ">=6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "dev": true + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, + "optional": true, "engines": { - "node": ">= 0.8.0" + "node": ">=6" } }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "node_modules/piscina": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.6.1.tgz", + "integrity": "sha512-z30AwWGtQE+Apr+2WBZensP2lIvwoaMcOPkQlIEmSGMJNUvaYACylPYrQM6wSdUNJlnDVMSpLv7xTMJqlVshOA==", "dev": true, - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "nice-napi": "^1.0.2" } }, - "node_modules/ora/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "find-up": "^6.3.0" }, "engines": { - "node": ">=8" + "node": ">=14.16" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/pkg-dir/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "p-locate": "^6.0.0" }, "engines": { - "node": ">=7.0.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/ora/node_modules/has-flag": { + "node_modules/pkg-dir/node_modules/p-limit": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "p-limit": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/pkg-dir/node_modules/yocto-queue": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, "engines": { - "node": ">=6" + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/postcss": { + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "dependencies": { - "p-limit": "^2.2.0" + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" }, "engines": { - "node": ">=8" + "node": "^10 || ^12 || >=14" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "node_modules/postcss-loader": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", + "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", "dev": true, "dependencies": { - "aggregate-error": "^3.0.0" + "cosmiconfig": "^9.0.0", + "jiti": "^1.20.0", + "semver": "^7.5.4" }, "engines": { - "node": ">=10" + "node": ">= 18.12.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "dev": true, - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" + "type": "opencollective", + "url": "https://opencollective.com/webpack" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, - "node_modules/p-retry/node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, - "engines": { - "node": ">= 4" - } + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "dev": true, "engines": { - "node": ">=6" - } - }, - "node_modules/pacote": { - "version": "17.0.4", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-17.0.4.tgz", - "integrity": "sha512-eGdLHrV/g5b5MtD5cTPyss+JxOlaOloSMG3UwPMAvL8ywaLJ6beONPF40K4KKl/UI6q5hTKCJq5rCu8tkF+7Dg==", - "dev": true, - "dependencies": { - "@npmcli/git": "^5.0.0", - "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/promise-spawn": "^7.0.0", - "@npmcli/run-script": "^7.0.0", - "cacache": "^18.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^7.0.2", - "npm-package-arg": "^11.0.0", - "npm-packlist": "^8.0.0", - "npm-pick-manifest": "^9.0.0", - "npm-registry-fetch": "^16.0.0", - "proc-log": "^3.0.0", - "promise-retry": "^2.0.1", - "read-package-json": "^7.0.0", - "read-package-json-fast": "^3.0.0", - "sigstore": "^2.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11" - }, - "bin": { - "pacote": "lib/bin.js" + "node": "^10 || ^12 || >= 14" }, - "engines": { - "node": "^16.14.0 || >=18.0.0" + "peerDependencies": { + "postcss": "^8.1.0" } }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", "dev": true, "dependencies": { - "callsites": "^3.0.0" + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" }, "engines": { - "node": ">=6" + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" } }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "node_modules/postcss-modules-scope": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" + "postcss-selector-parser": "^6.0.4" }, "engines": { - "node": ">=8" + "node": "^10 || ^12 || >= 14" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "postcss": "^8.1.0" } }, - "node_modules/parse-json/node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/parse-json/node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "devOptional": true, "dependencies": { - "entities": "^4.4.0" + "icss-utils": "^5.0.0" }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" } }, - "node_modules/parse5-html-rewriting-stream": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-7.0.0.tgz", - "integrity": "sha512-mazCyGWkmCRWDI15Zp+UiCqMp/0dgEmkZRvhlsqqKYr4SsVm/TvnSpD9fCvqCA2zoWJcfRym846ejWBBHRiYEg==", + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "dev": true, "dependencies": { - "entities": "^4.3.0", - "parse5": "^7.0.0", - "parse5-sax-parser": "^7.0.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "engines": { + "node": ">=4" } }, - "node_modules/parse5-sax-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", - "integrity": "sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg==", + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, - "dependencies": { - "parse5": "^7.0.0" + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" }, "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "node_modules/prettier-eslint": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-13.0.0.tgz", + "integrity": "sha512-P5K31qWgUOQCtJL/3tpvEe28KfP49qbr6MTVEXC7I2k7ci55bP3YDr+glhyCdhIzxGCVp2f8eobfQ5so52RIIA==", "dev": true, + "dependencies": { + "@typescript-eslint/parser": "^3.0.0", + "common-tags": "^1.4.0", + "dlv": "^1.1.0", + "eslint": "^7.9.0", + "indent-string": "^4.0.0", + "lodash.merge": "^4.6.0", + "loglevel-colored-level-prefix": "^1.0.0", + "prettier": "^2.0.0", + "pretty-format": "^23.0.1", + "require-relative": "^0.8.7", + "typescript": "^3.9.3", + "vue-eslint-parser": "~7.1.0" + }, "engines": { - "node": ">= 0.8" + "node": ">=10.0.0" } }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/prettier-eslint/node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", "dev": true, - "engines": { - "node": ">=8" + "dependencies": { + "@babel/highlight": "^7.10.4" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "node_modules/prettier-eslint/node_modules/@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, "engines": { - "node": ">=0.10.0" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/prettier-eslint/node_modules/@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "deprecated": "Use @eslint/config-array instead", "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, "engines": { - "node": ">=8" + "node": ">=10.10.0" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "node_modules/prettier-eslint/node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true }, - "node_modules/path-scurry": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", - "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "node_modules/prettier-eslint/node_modules/@typescript-eslint/parser": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.10.1.tgz", + "integrity": "sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw==", "dev": true, "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "@types/eslint-visitor-keys": "^1.0.0", + "@typescript-eslint/experimental-utils": "3.10.1", + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/typescript-estree": "3.10.1", + "eslint-visitor-keys": "^1.1.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "^10.12.0 || >=12.0.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "node_modules/prettier-eslint/node_modules/@typescript-eslint/types": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", + "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==", "dev": true, "engines": { - "node": "14 || >=16.14" + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", - "dev": true - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "node_modules/prettier-eslint/node_modules/@typescript-eslint/typescript-estree": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", + "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", "dev": true, + "dependencies": { + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/visitor-keys": "3.10.1", + "debug": "^4.1.1", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, "engines": { - "node": ">=8" + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", - "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", + "node_modules/prettier-eslint/node_modules/@typescript-eslint/visitor-keys": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", + "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, "engines": { - "node": ">=12" + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "node_modules/prettier-eslint/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true, - "optional": true, + "bin": { + "acorn": "bin/acorn" + }, "engines": { - "node": ">=6" + "node": ">=0.4.0" } }, - "node_modules/piscina": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.4.0.tgz", - "integrity": "sha512-+AQduEJefrOApE4bV7KRmp3N2JnnyErlVqq4P/jmko4FPz9Z877BCccl/iB3FdrWSUkvbGV9Kan/KllJgat3Vg==", + "node_modules/prettier-eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "optionalDependencies": { - "nice-napi": "^1.0.2" + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/pkg-dir": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", - "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "node_modules/prettier-eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "find-up": "^6.3.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=14.16" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "node_modules/prettier-eslint/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "sprintf-js": "~1.0.2" } }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "node_modules/prettier-eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "dependencies": { - "p-locate": "^6.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/prettier-eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "node_modules/prettier-eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { - "yocto-queue": "^1.0.0" + "color-name": "~1.1.4" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=7.0.0" + } + }, + "node_modules/prettier-eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/prettier-eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "node_modules/prettier-eslint/node_modules/eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", "dev": true, "dependencies": { - "p-limit": "^4.0.0" + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": "^10.12.0 || >=12.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "node_modules/pkg-dir/node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "node_modules/prettier-eslint/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=8.0.0" } }, - "node_modules/pkg-dir/node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "node_modules/prettier-eslint/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true, "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, - "node_modules/postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "node_modules/prettier-eslint/node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=10" } }, - "node_modules/postcss-loader": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", - "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", + "node_modules/prettier-eslint/node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "dev": true, "dependencies": { - "cosmiconfig": "^9.0.0", - "jiti": "^1.20.0", - "semver": "^7.5.4" + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" }, "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "postcss": "^7.0.0 || ^8.0.1", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/postcss-media-query-parser": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", - "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", - "dev": true - }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", - "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "node_modules/prettier-eslint/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" + "node": ">=4.0" } }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", - "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", + "node_modules/prettier-eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" + "type-fest": "^0.20.2" }, "engines": { - "node": "^10 || ^12 || >= 14" + "node": ">=8" }, - "peerDependencies": { - "postcss": "^8.1.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/postcss-modules-scope": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", - "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "node_modules/prettier-eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.4" - }, "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" + "node": ">=8" } }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "node_modules/prettier-eslint/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true, - "dependencies": { - "icss-utils": "^5.0.0" - }, "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" + "node": ">= 4" } }, - "node_modules/postcss-selector-parser": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", - "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "node_modules/prettier-eslint/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, - "engines": { - "node": ">=4" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "node_modules/prettier-eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "node_modules/prettier-eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">= 0.8.0" + "node": "*" } }, - "node_modules/prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "node_modules/prettier-eslint/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, "bin": { - "prettier": "bin/prettier.cjs" + "prettier": "bin-prettier.js" }, "engines": { - "node": ">=14" + "node": ">=10.13.0" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/prettier-eslint": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-16.3.0.tgz", - "integrity": "sha512-Lh102TIFCr11PJKUMQ2kwNmxGhTsv/KzUg9QYF2Gkw259g/kPgndZDWavk7/ycbRvj2oz4BPZ1gCU8bhfZH/Xg==", + "node_modules/prettier-eslint/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/prettier-eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "@typescript-eslint/parser": "^6.7.5", - "common-tags": "^1.4.0", - "dlv": "^1.1.0", - "eslint": "^8.7.0", - "indent-string": "^4.0.0", - "lodash.merge": "^4.6.0", - "loglevel-colored-level-prefix": "^1.0.0", - "prettier": "^3.0.1", - "pretty-format": "^29.7.0", - "require-relative": "^0.8.7", - "typescript": "^5.2.2", - "vue-eslint-parser": "^9.1.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=16.10.0" + "node": ">=8" + } + }, + "node_modules/prettier-eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" }, - "peerDependencies": { - "prettier-plugin-svelte": "^3.0.0", - "svelte-eslint-parser": "*" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prettier-eslint/node_modules/typescript": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", + "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, - "peerDependenciesMeta": { - "prettier-plugin-svelte": { - "optional": true - }, - "svelte-eslint-parser": { - "optional": true - } + "engines": { + "node": ">=4.2.0" } }, "node_modules/prettier-linter-helpers": { @@ -13284,37 +12404,21 @@ "engines": { "node": ">=6.0.0" } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + }, + "node_modules/pretty-format": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", + "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0", + "ansi-styles": "^3.2.0" } }, "node_modules/proc-log": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", - "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -13325,6 +12429,15 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -13366,12 +12479,6 @@ "node": ">= 0.10" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, "node_modules/prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -13380,22 +12487,19 @@ "optional": true }, "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true }, "node_modules/qjobs": { "version": "1.2.0", @@ -13407,12 +12511,12 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dev": true, "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -13474,62 +12578,6 @@ "node": ">= 0.8" } }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, - "node_modules/read-package-json": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-7.0.1.tgz", - "integrity": "sha512-8PcDiZ8DXUjLf687Ol4BR8Bpm2umR7vhoZOzNRt+uxD9GpBh/K+CAAALVIiYFknmvlmyg7hM7BSNUXPaCCqd0Q==", - "dev": true, - "dependencies": { - "glob": "^10.2.2", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/read-package-json-fast": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", - "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", - "dev": true, - "dependencies": { - "json-parse-even-better-errors": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/read-package-json/node_modules/glob": { - "version": "10.3.12", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", - "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.10.2" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -13580,9 +12628,9 @@ "dev": true }, "node_modules/regenerate-unicode-properties": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", - "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", "dev": true, "dependencies": { "regenerate": "^1.4.2" @@ -13612,6 +12660,18 @@ "integrity": "sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg==", "dev": true }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, "node_modules/regexpu-core": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", @@ -13703,12 +12763,12 @@ } }, "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "engines": { - "node": ">=8" + "node": ">=4" } }, "node_modules/resolve-url-loader": { @@ -13751,16 +12811,19 @@ } }, "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/retry": { @@ -13783,14 +12846,15 @@ } }, "node_modules/rfdc": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", - "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.1.3" @@ -13803,9 +12867,9 @@ } }, "node_modules/rollup": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz", - "integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", + "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -13818,32 +12882,35 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.17.2", - "@rollup/rollup-android-arm64": "4.17.2", - "@rollup/rollup-darwin-arm64": "4.17.2", - "@rollup/rollup-darwin-x64": "4.17.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.17.2", - "@rollup/rollup-linux-arm-musleabihf": "4.17.2", - "@rollup/rollup-linux-arm64-gnu": "4.17.2", - "@rollup/rollup-linux-arm64-musl": "4.17.2", - "@rollup/rollup-linux-powerpc64le-gnu": "4.17.2", - "@rollup/rollup-linux-riscv64-gnu": "4.17.2", - "@rollup/rollup-linux-s390x-gnu": "4.17.2", - "@rollup/rollup-linux-x64-gnu": "4.17.2", - "@rollup/rollup-linux-x64-musl": "4.17.2", - "@rollup/rollup-win32-arm64-msvc": "4.17.2", - "@rollup/rollup-win32-ia32-msvc": "4.17.2", - "@rollup/rollup-win32-x64-msvc": "4.17.2", + "@rollup/rollup-android-arm-eabi": "4.22.4", + "@rollup/rollup-android-arm64": "4.22.4", + "@rollup/rollup-darwin-arm64": "4.22.4", + "@rollup/rollup-darwin-x64": "4.22.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", + "@rollup/rollup-linux-arm-musleabihf": "4.22.4", + "@rollup/rollup-linux-arm64-gnu": "4.22.4", + "@rollup/rollup-linux-arm64-musl": "4.22.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", + "@rollup/rollup-linux-riscv64-gnu": "4.22.4", + "@rollup/rollup-linux-s390x-gnu": "4.22.4", + "@rollup/rollup-linux-x64-gnu": "4.22.4", + "@rollup/rollup-linux-x64-musl": "4.22.4", + "@rollup/rollup-win32-arm64-msvc": "4.22.4", + "@rollup/rollup-win32-ia32-msvc": "4.22.4", + "@rollup/rollup-win32-x64-msvc": "4.22.4", "fsevents": "~2.3.2" } }, - "node_modules/run-async": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", - "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", "dev": true, "engines": { - "node": ">=0.12.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/run-parallel": { @@ -13902,15 +12969,10 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, - "node_modules/safevalues": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/safevalues/-/safevalues-0.3.4.tgz", - "integrity": "sha512-LRneZZRXNgjzwG4bDQdOTSbze3fHm1EAKN/8bePxnlEZiBmkYEDggaHbuvHI9/hoqHbGfsEA7tWS9GhYHZBBsw==" - }, "node_modules/sass": { - "version": "1.71.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", - "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==", + "version": "1.77.6", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz", + "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -13925,9 +12987,9 @@ } }, "node_modules/sass-loader": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.1.1.tgz", - "integrity": "sha512-QX8AasDg75monlybel38BZ49JP5Z+uSKfKwF2rO7S74BywaRmGQMUBw9dtkS+ekyM/QnP+NOrRYq8ABMZ9G8jw==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.0.tgz", + "integrity": "sha512-n13Z+3rU9A177dk4888czcVFiC8CL9dii4qpXWUg3YIIgZEvi9TCFKjOQcbK0kJM7DJu9VucrZFddvNfYCPwtw==", "dev": true, "dependencies": { "neo-async": "^2.6.2" @@ -13965,9 +13027,9 @@ } }, "node_modules/sax": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", - "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", "dev": true, "optional": true }, @@ -13990,6 +13052,23 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/schema-utils/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -14010,13 +13089,10 @@ } }, "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -14024,28 +13100,10 @@ "node": ">=10" } }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dev": true, "dependencies": { "debug": "2.6.9", @@ -14093,12 +13151,6 @@ "node": ">=4" } }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, "node_modules/send/node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -14187,20 +13239,29 @@ "dev": true }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dev": true, "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -14285,35 +13346,72 @@ } }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/sigstore": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.0.tgz", - "integrity": "sha512-q+o8L2ebiWD1AxD17eglf1pFrl9jtW7FHa0ygqY6EKvibK8JHyq9Z26v9MZXeDiw+RbfOJ9j2v70M10Hd6E06A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz", + "integrity": "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==", "dev": true, "dependencies": { - "@sigstore/bundle": "^2.3.1", + "@sigstore/bundle": "^2.3.2", "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.1", - "@sigstore/sign": "^2.3.0", - "@sigstore/tuf": "^2.3.1", - "@sigstore/verify": "^1.2.0" + "@sigstore/protobuf-specs": "^0.3.2", + "@sigstore/sign": "^2.3.2", + "@sigstore/tuf": "^2.3.4", + "@sigstore/verify": "^1.2.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", "dev": true, "engines": { - "node": ">=8" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/smart-buffer": { @@ -14327,16 +13425,16 @@ } }, "node_modules/socket.io": { - "version": "4.7.5", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", - "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.0.tgz", + "integrity": "sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA==", "dev": true, "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", - "engine.io": "~6.5.2", + "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" }, @@ -14354,6 +13452,27 @@ "ws": "~8.17.1" } }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/socket.io-parser": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", @@ -14393,14 +13512,14 @@ } }, "node_modules/socks-proxy-agent": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz", - "integrity": "sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", + "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", "dev": true, "dependencies": { "agent-base": "^7.1.1", "debug": "^4.3.4", - "socks": "^2.7.1" + "socks": "^2.8.3" }, "engines": { "node": ">= 14" @@ -14416,9 +13535,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -14502,9 +13621,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", - "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", + "version": "3.0.20", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", + "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", "dev": true }, "node_modules/spdy": { @@ -14546,9 +13665,9 @@ } }, "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "dev": true }, "node_modules/ssri": { @@ -14580,47 +13699,15 @@ "node_modules/streamroller": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", - "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", - "dev": true, - "dependencies": { - "date-format": "^4.0.14", - "debug": "^4.3.4", - "fs-extra": "^8.1.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/streamroller/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/streamroller/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/streamroller/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, "engines": { - "node": ">= 4.0.0" + "node": ">=8.0" } }, "node_modules/string_decoder": { @@ -14632,17 +13719,20 @@ } }, "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/string-width-cjs": { @@ -14660,6 +13750,48 @@ "node": ">=8" } }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -14685,13 +13817,22 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "engines": { - "node": ">=4" + "node": ">=8" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" } }, "node_modules/strip-final-newline": { @@ -14715,23 +13856,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strong-log-transformer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz", - "integrity": "sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA==", - "dev": true, - "dependencies": { - "duplexer": "^0.1.1", - "minimist": "^1.2.0", - "through": "^2.3.4" - }, - "bin": { - "sl-log-transformer": "bin/sl-log-transformer.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -14766,9 +13890,9 @@ } }, "node_modules/synckit": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", - "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", + "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==", "dev": true, "dependencies": { "@pkgr/core": "^0.1.0", @@ -14781,6 +13905,101 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/table": { + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", + "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/table/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/table/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/table/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/table/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/table/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -14807,22 +14026,6 @@ "node": ">=10" } }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/tar/node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -14875,9 +14078,9 @@ "dev": true }, "node_modules/terser": { - "version": "5.29.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.29.1.tgz", - "integrity": "sha512-lZQ/fyaIGxsbGxApKmoPTODIzELy3++mXhS5hOqaAWZjQtpq/hFHAc+rm29NND1rYRxRWKcjuARNwULNXa5RtQ==", + "version": "5.31.6", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz", + "integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -14975,53 +14178,23 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true + "node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "dev": true, + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } }, "node_modules/thunky": { "version": "1.1.0", @@ -15030,15 +14203,15 @@ "dev": true }, "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "dependencies": { - "rimraf": "^3.0.0" + "os-tmpdir": "~1.0.2" }, "engines": { - "node": ">=8.17.0" + "node": ">=0.6.0" } }, "node_modules/to-fast-properties": { @@ -15071,6 +14244,22 @@ "node": ">=0.6" } }, + "node_modules/tree-dump": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz", + "integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -15092,24 +14281,31 @@ "typescript": ">=4.2.0" } }, - "node_modules/tsconfig-paths": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" + "tslib": "^1.8.1" }, "engines": { - "node": ">=6" + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true }, "node_modules/tuf-js": { "version": "2.2.1", @@ -15174,9 +14370,9 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -15187,9 +14383,9 @@ } }, "node_modules/ua-parser-js": { - "version": "0.7.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.37.tgz", - "integrity": "sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA==", + "version": "0.7.39", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.39.tgz", + "integrity": "sha512-IZ6acm6RhQHNibSt7+c09hhvsKy9WUr4DVbeq9U8o71qxyYtJpQeDxQnMrVqnIFMLcQjHO0I9wgfO2vIahht4w==", "dev": true, "funding": [ { @@ -15205,29 +14401,23 @@ "url": "https://github.com/sponsors/faisalman" } ], + "bin": { + "ua-parser-js": "script/cli.js" + }, "engines": { "node": "*" } }, - "node_modules/undici": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.11.1.tgz", - "integrity": "sha512-KyhzaLJnV1qa3BSHdj4AZ2ndqI0QWPxYzaIOio0WzcEJB9gvuysprJSLtpvc2D9mhR9jPDUk7xlJlZbH2KR5iw==", - "dev": true, - "engines": { - "node": ">=18.0" - } - }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true }, "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", "dev": true, "engines": { "node": ">=4" @@ -15247,9 +14437,9 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", "dev": true, "engines": { "node": ">=4" @@ -15264,6 +14454,18 @@ "node": ">=4" } }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unique-filename": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", @@ -15289,12 +14491,12 @@ } }, "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, "engines": { - "node": ">= 10.0.0" + "node": ">= 4.0.0" } }, "node_modules/unpipe": { @@ -15307,9 +14509,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.15.tgz", - "integrity": "sha512-K9HWH62x3/EalU1U6sjSZiylm9C8tgq2mSvshZpqc7QE69RaA2qjhkW2HlNA0tFpEbtyFz7HTqbSdN4MSwUodA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "dev": true, "funding": [ { @@ -15326,8 +14528,8 @@ } ], "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -15345,6 +14547,15 @@ "punycode": "^2.1.0" } }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -15368,6 +14579,12 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", + "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", + "dev": true + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -15397,14 +14614,14 @@ } }, "node_modules/vite": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.7.tgz", - "integrity": "sha512-sgnEEFTZYMui/sTlH1/XEnVNHMujOahPLGMxn1+5sIT45Xjng1Ec1K78jRP15dSmVgg5WBin9yO81j3o9OxofA==", + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", + "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", "dev": true, "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.35", - "rollup": "^4.2.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -15423,6 +14640,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -15440,6 +14658,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, @@ -15452,9 +14673,9 @@ } }, "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], @@ -15468,9 +14689,9 @@ } }, "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], @@ -15484,9 +14705,9 @@ } }, "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], @@ -15500,9 +14721,9 @@ } }, "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], @@ -15516,9 +14737,9 @@ } }, "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], @@ -15532,9 +14753,9 @@ } }, "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], @@ -15548,9 +14769,9 @@ } }, "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], @@ -15564,9 +14785,9 @@ } }, "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], @@ -15580,9 +14801,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], @@ -15596,9 +14817,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], @@ -15612,9 +14833,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], @@ -15628,9 +14849,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], @@ -15644,9 +14865,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], @@ -15660,9 +14881,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], @@ -15676,9 +14897,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], @@ -15692,9 +14913,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], @@ -15708,9 +14929,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -15724,9 +14945,9 @@ } }, "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], @@ -15740,9 +14961,9 @@ } }, "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], @@ -15756,9 +14977,9 @@ } }, "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], @@ -15772,9 +14993,9 @@ } }, "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], @@ -15788,9 +15009,9 @@ } }, "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], @@ -15804,9 +15025,9 @@ } }, "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], @@ -15820,9 +15041,9 @@ } }, "node_modules/vite/node_modules/esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, "bin": { @@ -15832,29 +15053,57 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vite/node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" } }, "node_modules/void-elements": { @@ -15867,49 +15116,89 @@ } }, "node_modules/vue-eslint-parser": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz", - "integrity": "sha512-Ry9oiGmCAK91HrKMtCrKFWmSFWvYkpGglCeFAIqDdr9zdXmMMpJOmUJS7WWsW7fX81h6mwHmUZCQQ1E0PkSwYQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.1.1.tgz", + "integrity": "sha512-8FdXi0gieEwh1IprIBafpiJWcApwrU+l2FEj8c1HtHFdNXMd0+2jUSjBVmcQYohf/E72irwAXEXLga6TQcB3FA==", "dev": true, "dependencies": { - "debug": "^4.3.4", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.1", - "esquery": "^1.4.0", - "lodash": "^4.17.21", - "semver": "^7.3.6" + "debug": "^4.1.1", + "eslint-scope": "^5.0.0", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.2.1", + "esquery": "^1.0.1", + "lodash": "^4.17.15" }, "engines": { - "node": "^14.17.0 || >=16.0.0" + "node": ">=8.10" }, "funding": { "url": "https://github.com/sponsors/mysticatea" }, "peerDependencies": { - "eslint": ">=6.0.0" + "eslint": ">=5.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" } }, "node_modules/vue-eslint-parser/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "estraverse": "^4.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=8.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/vue-eslint-parser/node_modules/espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "dependencies": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" } }, "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", + "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", "dev": true, "dependencies": { "glob-to-regexp": "^0.4.1", @@ -15937,27 +15226,32 @@ "defaults": "^1.0.3" } }, + "node_modules/weak-lru-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", + "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", + "dev": true + }, "node_modules/webpack": { - "version": "5.90.3", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz", - "integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==", + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, "dependencies": { - "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", + "acorn-import-attributes": "^1.9.5", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", + "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", @@ -15965,7 +15259,7 @@ "schema-utils": "^3.2.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.3.10", - "watchpack": "^2.4.0", + "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { @@ -15985,19 +15279,20 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.1.2.tgz", - "integrity": "sha512-Wu+EHmX326YPYUpQLKmKbTyZZJIB8/n6R09pTmB03kJmnMsVPTo9COzHZFr01txwaCAuZvfBJE4ZCHRcKs5JaQ==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", + "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", "dev": true, "dependencies": { "colorette": "^2.0.10", - "memfs": "^3.4.12", + "memfs": "^4.6.0", "mime-types": "^2.1.31", + "on-finished": "^2.4.1", "range-parser": "^1.2.1", "schema-utils": "^4.0.0" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", @@ -16013,54 +15308,54 @@ } }, "node_modules/webpack-dev-server": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", - "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", - "dev": true, - "dependencies": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/serve-static": "^1.13.10", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.5", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.0.4.tgz", + "integrity": "sha512-dljXhUgx3HqKP2d8J/fUMvhxGhzjeNVarDLcbO/EWMSgRizDkxHQDZQaLFL5VJY9tRBj2Gz+rvCEYYvhbqPHNA==", + "dev": true, + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.0.11", - "chokidar": "^3.5.3", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", "colorette": "^2.0.10", "compression": "^1.7.4", "connect-history-api-fallback": "^2.0.0", "default-gateway": "^6.0.3", "express": "^4.17.3", "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", + "html-entities": "^2.4.0", "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.0.1", - "launch-editor": "^2.6.0", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "rimraf": "^3.0.2", - "schema-utils": "^4.0.0", - "selfsigned": "^2.1.1", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "rimraf": "^5.0.5", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", "serve-index": "^1.9.1", "sockjs": "^0.3.24", "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", - "ws": "^8.13.0" + "webpack-dev-middleware": "^7.1.0", + "ws": "^8.16.0" }, "bin": { "webpack-dev-server": "bin/webpack-dev-server.js" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^4.37.0 || ^5.0.0" + "webpack": "^5.0.0" }, "peerDependenciesMeta": { "webpack": { @@ -16071,41 +15366,98 @@ } } }, - "node_modules/webpack-dev-server/node_modules/webpack-dev-middleware": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", - "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", + "node_modules/webpack-dev-server/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "dependencies": { - "colorette": "^2.0.10", - "memfs": "^3.4.3", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" }, "engines": { - "node": ">= 12.13.0" + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" }, "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", "dev": true, "dependencies": { "clone-deep": "^4.0.1", "flat": "^5.0.2", - "wildcard": "^2.0.0" + "wildcard": "^2.0.1" }, "engines": { - "node": ">=10.0.0" + "node": ">=18.0.0" } }, "node_modules/webpack-sources": { @@ -16333,6 +15685,35 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -16366,22 +15747,50 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "dev": true, + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "engines": { - "node": ">=10.0.0" + "node": ">=8.3.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" + "utf-8-validate": "^5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -16442,6 +15851,35 @@ "node": ">=12" } }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -16454,13 +15892,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/zone.js": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.5.tgz", - "integrity": "sha512-9XYWZzY6PhHOSdkYryNcMm7L8EK7a4q+GbTvxbIA2a9lMdRUpGuyaYvLDcg8D6bdn+JomSsbPcilVKg6SmUx6w==", - "dependencies": { - "tslib": "^2.3.0" + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zone.js": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz", + "integrity": "sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ==" } } } diff --git a/modules/ui/package.json b/modules/ui/package.json index aceb9c389..b8f777d64 100644 --- a/modules/ui/package.json +++ b/modules/ui/package.json @@ -17,37 +17,37 @@ }, "private": true, "dependencies": { - "@angular/animations": "^17.0.8", - "@angular/cdk": "^17.0.4", - "@angular/common": "^17.0.8", - "@angular/compiler": "^17.0.8", - "@angular/core": "^17.0.8", - "@angular/forms": "^17.3.1", - "@angular/material": "^17.3.1", - "@angular/platform-browser": "^17.0.8", - "@angular/platform-browser-dynamic": "^17.3.1", - "@angular/router": "^17.3.1", - "@ngrx/component-store": "^17.1.1", - "@ngrx/effects": "^17.1.1", - "@ngrx/store": "^17.0.1", + "@angular/animations": "^18.2.4", + "@angular/cdk": "^18.2.0", + "@angular/common": "^18.2.4", + "@angular/compiler": "^18.2.4", + "@angular/core": "^18.2.4", + "@angular/forms": "^18.2.4", + "@angular/material": "^18.2.0", + "@angular/platform-browser": "^18.2.4", + "@angular/platform-browser-dynamic": "^18.2.4", + "@angular/router": "^18.2.4", + "@ngrx/component-store": "^18.0.2", + "@ngrx/effects": "^18.0.2", + "@ngrx/store": "^18.0.2", "ngx-mask": "^16.4.2", "ngx-mqtt": "^17.0.0", "rxjs": "~7.8.0", "tslib": "^2.6.2", - "zone.js": "^0.14.4" + "zone.js": "^0.14.10" }, "devDependencies": { - "@angular-devkit/build-angular": "^17.3.2", - "@angular-eslint/builder": "17.2.0", - "@angular-eslint/eslint-plugin": "17.2.0", - "@angular-eslint/eslint-plugin-template": "17.2.0", - "@angular-eslint/schematics": "17.2.0", - "@angular-eslint/template-parser": "17.2.0", - "@angular/cli": "~17.0.9", - "@angular/compiler-cli": "^17.3.1", + "@angular-devkit/build-angular": "^18.1.4", + "@angular-eslint/builder": "18.3.0", + "@angular-eslint/eslint-plugin": "^18.3.0", + "@angular-eslint/eslint-plugin-template": "^18.3.0", + "@angular-eslint/schematics": "^18.3.0", + "@angular-eslint/template-parser": "18.3.0", + "@angular/cli": "~18.2.4", + "@angular/compiler-cli": "^18.2.4", "@types/jasmine": "~4.3.6", - "@typescript-eslint/eslint-plugin": "^6.21.0", - "@typescript-eslint/parser": "^6.21.0", + "@typescript-eslint/eslint-plugin": "^8.2.0", + "@typescript-eslint/parser": "^8.2.0", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", @@ -58,7 +58,7 @@ "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", "prettier": "^3.2.5", - "prettier-eslint": "^16.3.0", - "typescript": "~5.2.2" + "prettier-eslint": "^13.0.0", + "typescript": "~5.5.4" } } diff --git a/modules/ui/src/app/app.component.html b/modules/ui/src/app/app.component.html index b1341a58d..912e40c32 100644 --- a/modules/ui/src/app/app.component.html +++ b/modules/ui/src/app/app.component.html @@ -29,7 +29,7 @@ route: Routes.Testing, svgIcon: 'testrun_logo_small', label: 'Testing', - name: 'testrun' + name: 'testrun', } "> @@ -40,7 +40,7 @@ route: Routes.Devices, svgIcon: 'devices', label: 'Devices', - name: 'devices' + name: 'devices', } "> @@ -51,7 +51,7 @@ route: Routes.Reports, svgIcon: 'reports', label: 'Reports', - name: 'reports' + name: 'reports', } "> @@ -62,17 +62,13 @@ route: Routes.RiskAssessment, svgIcon: 'risk_assessment', label: 'Risk Assessment', - name: 'risk-assessment' + name: 'risk-assessment', } "> + (consentShownEvent)="consentShown()">
@@ -128,19 +124,70 @@

Testrun

- - - - No ports detected. Please connect and configure network and device - connections in the +
+ + + + No ports detected. Please connect and configure network and + device connections in the + + + Selected port is missing! Please define a valid one using + + System settings + panel. + + + + Further information is required in your device configurations. + Please update your + Devices + to continue testing. - - Selected port is missing! Please define a valid one using - + + + Step 1: To perform a device test, please, select ports in Testrun > panel. - - - Step 1: To perform a device test, please, select ports in - System settings - panel. - - - Step 2: To perform a device test please - Create a Device - first. - - - Step 3: Once device is created, you are able to - start testing. - - - The device is now being tested. Why not take the time to complete the - device - Risk Assessment questionnaire? - + + Step 2: To perform a device test please + Create a Device + first. + + + Step 3: Once device is created, you are able to + start testing. + + + The device is now being tested. Why not take the time to complete + the device + Risk Assessment questionnaire? + +
@@ -262,6 +300,10 @@

Testrun

+ .mat-icon, @@ -133,8 +133,8 @@ $nav-open-btn-width: 210px; } .app-sidebar-button-active { - border: 1px solid mat.get-color-from-palette($color-primary, 500); - background-color: mat.get-color-from-palette($color-primary, 500); + border: 1px solid mat.m2-get-color-from-palette($color-primary, 500); + background-color: mat.m2-get-color-from-palette($color-primary, 500); } .app-sidebar-button-active > .mat-icon { @@ -186,7 +186,7 @@ $nav-open-btn-width: 210px; .app-content-main { position: relative; display: grid; - grid-template-rows: 0 auto; + grid-template-rows: auto 0 1fr; overflow: hidden; } diff --git a/modules/ui/src/app/app.component.spec.ts b/modules/ui/src/app/app.component.spec.ts index df531c8b7..fcad77213 100644 --- a/modules/ui/src/app/app.component.spec.ts +++ b/modules/ui/src/app/app.component.spec.ts @@ -54,13 +54,17 @@ import { selectError, selectHasConnectionSettings, selectHasDevices, + selectHasExpiredDevices, selectHasRiskProfiles, selectInterfaces, selectInternetConnection, + selectIsAllDevicesOutdated, selectIsOpenStartTestrun, selectIsOpenWaitSnackBar, + selectIsTestingComplete, selectMenuOpened, selectReports, + selectRiskProfiles, selectStatus, selectSystemStatus, } from './store/selectors'; @@ -74,6 +78,8 @@ import { TestRunMqttService } from './services/test-run-mqtt.service'; import { MOCK_ADAPTERS } from './mocks/settings.mock'; import { WifiComponent } from './components/wifi/wifi.component'; import { MatTooltipModule } from '@angular/material/tooltip'; +import { Profile } from './model/profile'; +import { TestrunStatus } from './model/testrun-status'; const windowMock = { location: { @@ -168,9 +174,13 @@ describe('AppComponent', () => { { selector: selectError, value: null }, { selector: selectMenuOpened, value: false }, { selector: selectHasDevices, value: false }, + { selector: selectIsAllDevicesOutdated, value: false }, + { selector: selectHasExpiredDevices, value: false }, { selector: selectHasRiskProfiles, value: false }, { selector: selectStatus, value: null }, { selector: selectSystemStatus, value: null }, + { selector: selectIsTestingComplete, value: false }, + { selector: selectRiskProfiles, value: [] }, { selector: selectIsOpenStartTestrun, value: false }, { selector: selectIsOpenWaitSnackBar, value: false }, { selector: selectReports, value: [] }, @@ -185,6 +195,7 @@ describe('AppComponent', () => { FakeSpinnerComponent, FakeShutdownAppComponent, FakeVersionComponent, + FakeTestingCompleteComponent, ], }); @@ -452,6 +463,21 @@ describe('AppComponent', () => { expect(internet).toBeTruthy(); }); + describe('Testing complete', () => { + beforeEach(() => { + store.overrideSelector(selectIsTestingComplete, true); + fixture.detectChanges(); + }); + + it('should have testing complete component', () => { + const testingCompleteComp = compiled.querySelector( + 'app-testing-complete' + ); + + expect(testingCompleteComp).toBeTruthy(); + }); + }); + describe('Callout component visibility', () => { describe('with no connection settings', () => { beforeEach(() => { @@ -768,6 +794,31 @@ describe('AppComponent', () => { }); }); }); + + describe('with expired devices', () => { + beforeEach(() => { + store.overrideSelector(selectHasExpiredDevices, true); + fixture.detectChanges(); + }); + + it('should have callout component', () => { + const callouts = compiled.querySelectorAll('app-callout'); + let hasExpiredDeviceCallout = false; + callouts.forEach(callout => { + if ( + callout?.innerHTML + .trim() + .includes( + 'Further information is required in your device configurations.' + ) + ) { + hasExpiredDeviceCallout = true; + } + }); + + expect(hasExpiredDeviceCallout).toBeTrue(); + }); + }); }); it('should not call toggleSettingsBtn focus on closeSetting when device length is 0', async () => { @@ -803,6 +854,15 @@ describe('AppComponent', () => { expect(component.certDrawer.open).toHaveBeenCalledTimes(1); }); + + it('should set focus to first focusable elem when close callout', fakeAsync(() => { + component.calloutClosed('mockId'); + tick(100); + + expect( + mockFocusManagerService.focusFirstElementInContainer + ).toHaveBeenCalled(); + })); }); @Component({ @@ -836,7 +896,14 @@ class FakeShutdownAppComponent { }) class FakeVersionComponent { @Input() consentShown!: boolean; - @Input() hasRiskProfiles!: boolean; @Output() consentShownEvent = new EventEmitter(); - @Output() navigateToRiskAssessmentEvent = new EventEmitter(); +} + +@Component({ + selector: 'app-testing-complete', + template: '
', +}) +class FakeTestingCompleteComponent { + @Input() profiles: Profile[] = []; + @Input() data!: TestrunStatus | null; } diff --git a/modules/ui/src/app/app.component.ts b/modules/ui/src/app/app.component.ts index 2214b8927..7218459c4 100644 --- a/modules/ui/src/app/app.component.ts +++ b/modules/ui/src/app/app.component.ts @@ -13,7 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Component, ElementRef, ViewChild } from '@angular/core'; +import { + AfterViewInit, + Component, + ElementRef, + QueryList, + ViewChild, + ViewChildren, +} from '@angular/core'; import { MatIconRegistry } from '@angular/material/icon'; import { DomSanitizer } from '@angular/platform-browser'; import { MatDrawer } from '@angular/material/sidenav'; @@ -35,6 +42,7 @@ import { AppStore } from './app.store'; import { TestRunService } from './services/test-run.service'; import { LiveAnnouncer } from '@angular/cdk/a11y'; import { filter, take } from 'rxjs/operators'; +import { skip, timer } from 'rxjs'; const DEVICES_LOGO_URL = '/assets/icons/devices.svg'; const DEVICES_RUN_URL = '/assets/icons/device_run.svg'; @@ -44,6 +52,8 @@ const TESTRUN_LOGO_URL = '/assets/icons/testrun_logo_small.svg'; const TESTRUN_LOGO_COLOR_URL = '/assets/icons/testrun_logo_color.svg'; const CLOSE_URL = '/assets/icons/close.svg'; const DRAFT_URL = '/assets/icons/draft.svg'; +const PILOT_URL = '/assets/icons/pilot.svg'; +const QUALIFICATION_URL = '/assets/icons/qualification.svg'; @Component({ selector: 'app-root', @@ -51,7 +61,7 @@ const DRAFT_URL = '/assets/icons/draft.svg'; styleUrls: ['./app.component.scss'], providers: [AppStore], }) -export class AppComponent { +export class AppComponent implements AfterViewInit { public readonly CalloutType = CalloutType; public readonly StatusOfTestrun = StatusOfTestrun; public readonly Routes = Routes; @@ -64,6 +74,8 @@ export class AppComponent { public toggleCertificatesBtn!: HTMLButtonElement; @ViewChild('navigation') public navigation!: ElementRef; @ViewChild('settings') public settings!: SettingsComponent; + @ViewChildren('riskAssessmentLink') + riskAssessmentLink!: QueryList; viewModel$ = this.appStore.viewModel$; constructor( @@ -115,14 +127,49 @@ export class AppComponent { 'draft', this.domSanitizer.bypassSecurityTrustResourceUrl(DRAFT_URL) ); + this.matIconRegistry.addSvgIcon( + 'pilot', + this.domSanitizer.bypassSecurityTrustResourceUrl(PILOT_URL) + ); + this.matIconRegistry.addSvgIcon( + 'qualification', + this.domSanitizer.bypassSecurityTrustResourceUrl(QUALIFICATION_URL) + ); + } + + ngAfterViewInit() { + this.viewModel$ + .pipe( + filter(({ isStatusLoaded }) => isStatusLoaded === true), + take(1) + ) + .subscribe(({ systemStatus }) => { + let skipCount = 0; + if (systemStatus === StatusOfTestrun.InProgress) { + // link should not be focused after page is just loaded + skipCount = 1; + } + this.riskAssessmentLink.changes.pipe(skip(skipCount)).subscribe(() => { + if (this.riskAssessmentLink.length > 0) { + this.riskAssessmentLink.first.nativeElement.focus(); + } + }); + }); } get isRiskAssessmentRoute(): boolean { return this.route.url === Routes.RiskAssessment; } + get isDevicesRoute(): boolean { + return this.route.url === Routes.Devices; + } + navigateToDeviceRepository(): void { this.route.navigate([Routes.Devices]); + } + navigateToAddDevice(): void { + this.route.navigate([Routes.Devices]); this.store.dispatch(setIsOpenAddDevice({ isOpenAddDevice: true })); } @@ -132,7 +179,9 @@ export class AppComponent { } navigateToRiskAssessment(): void { - this.route.navigate([Routes.RiskAssessment]); + this.route.navigate([Routes.RiskAssessment]).then(() => { + this.appStore.setFocusOnPage(); + }); } async closeCertificates(): Promise { @@ -207,4 +256,13 @@ export class AppComponent { this.appStore.setFocusOnPage(); }); } + + calloutClosed(id: string | null) { + if (id) { + this.appStore.setCloseCallout(id); + timer(100).subscribe(() => { + this.focusManagerService.focusFirstElementInContainer(); + }); + } + } } diff --git a/modules/ui/src/app/app.module.ts b/modules/ui/src/app/app.module.ts index 795d4e0d8..4ab788cbc 100644 --- a/modules/ui/src/app/app.module.ts +++ b/modules/ui/src/app/app.module.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; -import { NgModule } from '@angular/core'; +import { importProvidersFrom, NgModule } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatButtonToggleModule } from '@angular/material/button-toggle'; import { MatIconModule } from '@angular/material/icon'; @@ -50,11 +50,13 @@ import { WindowProvider } from './providers/window.provider'; import { CertificatesComponent } from './pages/certificates/certificates.component'; import { LOADER_TIMEOUT_CONFIG_TOKEN } from './services/loaderConfig'; import { WifiComponent } from './components/wifi/wifi.component'; +import { TestingCompleteComponent } from './components/testing-complete/testing-complete.component'; import { MqttModule, IMqttServiceOptions } from 'ngx-mqtt'; +import { MatNativeDateModule } from '@angular/material/core'; export const MQTT_SERVICE_OPTIONS: IMqttServiceOptions = { - hostname: 'localhost', + hostname: window.location.hostname, port: 9001, }; @@ -89,6 +91,7 @@ export const MQTT_SERVICE_OPTIONS: IMqttServiceOptions = { CertificatesComponent, MqttModule.forRoot(MQTT_SERVICE_OPTIONS), WifiComponent, + TestingCompleteComponent, ], providers: [ WindowProvider, @@ -103,6 +106,7 @@ export const MQTT_SERVICE_OPTIONS: IMqttServiceOptions = { multi: true, }, { provide: LOADER_TIMEOUT_CONFIG_TOKEN, useValue: 1000 }, + importProvidersFrom(MatNativeDateModule), ], bootstrap: [AppComponent], }) diff --git a/modules/ui/src/app/app.store.spec.ts b/modules/ui/src/app/app.store.spec.ts index e26db7eb3..4236b24fd 100644 --- a/modules/ui/src/app/app.store.spec.ts +++ b/modules/ui/src/app/app.store.spec.ts @@ -15,20 +15,25 @@ */ import { fakeAsync, TestBed, tick } from '@angular/core/testing'; import { of, skip, take } from 'rxjs'; -import { AppStore, CONSENT_SHOWN_KEY } from './app.store'; +import { AppStore, CALLOUT_STATE_KEY, CONSENT_SHOWN_KEY } from './app.store'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { AppState } from './store/state'; import { selectError, selectHasConnectionSettings, selectHasDevices, + selectHasExpiredDevices, selectHasRiskProfiles, selectInterfaces, selectInternetConnection, + selectIsAllDevicesOutdated, selectIsOpenWaitSnackBar, + selectIsTestingComplete, selectMenuOpened, selectReports, + selectRiskProfiles, selectStatus, + selectSystemStatus, selectTestModules, } from './store/selectors'; import { TestRunService } from './services/test-run.service'; @@ -58,6 +63,12 @@ const mock = (() => { setItem: (key: string, value: string) => { store[key] = value + ''; }, + getObject: (key: string) => { + return store[key] || null; + }, + setObject: (key: string, value: object) => { + store[key] = JSON.stringify(value); + }, clear: () => { store = {}; }, @@ -97,6 +108,10 @@ describe('AppStore', () => { { selector: selectIsOpenWaitSnackBar, value: false }, { selector: selectTestModules, value: MOCK_TEST_MODULES }, { selector: selectInternetConnection, value: false }, + { selector: selectIsAllDevicesOutdated, value: false }, + { selector: selectSystemStatus, value: null }, + { selector: selectIsTestingComplete, value: false }, + { selector: selectRiskProfiles, value: [] }, ], }), { provide: TestRunService, useValue: mockService }, @@ -111,6 +126,7 @@ describe('AppStore', () => { appStore = TestBed.inject(AppStore); store.overrideSelector(selectHasDevices, true); + store.overrideSelector(selectHasExpiredDevices, true); store.overrideSelector(selectHasRiskProfiles, false); store.overrideSelector(selectReports, []); store.overrideSelector(selectHasConnectionSettings, true); @@ -156,14 +172,20 @@ describe('AppStore', () => { expect(store).toEqual({ consentShown: false, hasDevices: true, + hasExpiredDevices: true, + isAllDevicesOutdated: false, hasRiskProfiles: false, reports: [], isStatusLoaded: false, systemStatus: null, + testrunStatus: null, + isTestingComplete: false, + riskProfiles: [], hasConnectionSettings: true, isMenuOpen: true, interfaces: {}, settingMissedError: null, + calloutState: new Map(), hasInternetConnection: false, }); done(); @@ -306,5 +328,22 @@ describe('AppStore', () => { ); }); }); + + describe('setCloseCallout', () => { + it('should update store', done => { + appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { + expect(store.calloutState.get('test')).toEqual(true); + done(); + }); + + appStore.setCloseCallout('test'); + }); + + it('should update storage', () => { + appStore.setCloseCallout('test'); + + expect(mock.getObject(CALLOUT_STATE_KEY)).toBeTruthy(); + }); + }); }); }); diff --git a/modules/ui/src/app/app.store.ts b/modules/ui/src/app/app.store.ts index 6e338968f..91280724c 100644 --- a/modules/ui/src/app/app.store.ts +++ b/modules/ui/src/app/app.store.ts @@ -21,12 +21,17 @@ import { selectError, selectHasConnectionSettings, selectHasDevices, + selectHasExpiredDevices, selectHasRiskProfiles, selectInterfaces, selectInternetConnection, + selectIsAllDevicesOutdated, + selectIsTestingComplete, selectMenuOpened, selectReports, + selectRiskProfiles, selectStatus, + selectSystemStatus, } from './store/selectors'; import { Store } from '@ngrx/store'; import { AppState } from './store/state'; @@ -51,19 +56,25 @@ import { import { FocusManagerService } from './services/focus-manager.service'; import { TestRunMqttService } from './services/test-run-mqtt.service'; import { NotificationService } from './services/notification.service'; +import { Profile } from './model/profile'; export const CONSENT_SHOWN_KEY = 'CONSENT_SHOWN'; +export const CALLOUT_STATE_KEY = 'CALLOUT_STATE'; export interface AppComponentState { consentShown: boolean; isStatusLoaded: boolean; systemStatus: TestrunStatus | null; + calloutState: Map; } @Injectable() export class AppStore extends ComponentStore { private consentShown$ = this.select(state => state.consentShown); + private calloutState$ = this.select(state => state.calloutState); private isStatusLoaded$ = this.select(state => state.isStatusLoaded); private hasInternetConnection$ = this.store.select(selectInternetConnection); private hasDevices$ = this.store.select(selectHasDevices); + private isAllDevicesOutdated$ = this.store.select(selectIsAllDevicesOutdated); + private hasExpiredDevices$ = this.store.select(selectHasExpiredDevices); private hasRiskProfiles$ = this.store.select(selectHasRiskProfiles); private reports$ = this.store.select(selectReports); private hasConnectionSetting$ = this.store.select( @@ -75,18 +86,30 @@ export class AppStore extends ComponentStore { private settingMissedError$: Observable = this.store.select(selectError); systemStatus$: Observable = this.store.select(selectStatus); + testrunStatus$: Observable = + this.store.select(selectSystemStatus); + isTestingComplete$: Observable = this.store.select( + selectIsTestingComplete + ); + riskProfiles$: Observable = this.store.select(selectRiskProfiles); viewModel$ = this.select({ consentShown: this.consentShown$, hasDevices: this.hasDevices$, + isAllDevicesOutdated: this.isAllDevicesOutdated$, + hasExpiredDevices: this.hasExpiredDevices$, hasRiskProfiles: this.hasRiskProfiles$, reports: this.reports$, isStatusLoaded: this.isStatusLoaded$, systemStatus: this.systemStatus$, + testrunStatus: this.testrunStatus$, + isTestingComplete: this.isTestingComplete$, + riskProfiles: this.riskProfiles$, hasConnectionSettings: this.hasConnectionSetting$, isMenuOpen: this.isMenuOpen$, interfaces: this.interfaces$, settingMissedError: this.settingMissedError$, + calloutState: this.calloutState$, hasInternetConnection: this.hasInternetConnection$, }); @@ -95,6 +118,17 @@ export class AppStore extends ComponentStore { consentShown, })); + updateCalloutState = this.updater((state, callout: string) => { + const calloutState = state.calloutState; + calloutState.set(callout, true); + // @ts-expect-error property is defined in index.html + sessionStorage.setObject(CALLOUT_STATE_KEY, calloutState); + return { + ...state, + calloutState: new Map(calloutState), + }; + }); + updateIsStatusLoaded = this.updater((state, isStatusLoaded: boolean) => ({ ...state, isStatusLoaded, @@ -217,6 +251,14 @@ export class AppStore extends ComponentStore { ); }); + setCloseCallout = this.effect(trigger$ => { + return trigger$.pipe( + tap((id: string) => { + this.updateCalloutState(id); + }) + ); + }); + constructor( private store: Store, private testRunService: TestRunService, @@ -224,10 +266,16 @@ export class AppStore extends ComponentStore { private focusManagerService: FocusManagerService, private notificationService: NotificationService ) { + // @ts-expect-error get object is defined in index.html + const calloutState = sessionStorage.getObject(CALLOUT_STATE_KEY); + super({ consentShown: sessionStorage.getItem(CONSENT_SHOWN_KEY) !== null, isStatusLoaded: false, systemStatus: null, + calloutState: calloutState + ? new Map(Object.entries(calloutState)) + : new Map(), }); } } diff --git a/modules/ui/src/app/components/callout/callout.component.html b/modules/ui/src/app/components/callout/callout.component.html index a3fb7930c..36f0c981f 100644 --- a/modules/ui/src/app/components/callout/callout.component.html +++ b/modules/ui/src/app/components/callout/callout.component.html @@ -15,12 +15,23 @@ -->
{{ type }} + +

+
diff --git a/modules/ui/src/app/components/callout/callout.component.scss b/modules/ui/src/app/components/callout/callout.component.scss index 8a9d77125..bd1008638 100644 --- a/modules/ui/src/app/components/callout/callout.component.scss +++ b/modules/ui/src/app/components/callout/callout.component.scss @@ -21,25 +21,13 @@ width: 100%; } -:host:has(.callout-container.info), -:host:has(.callout-container.error), -:host:has(.callout-container.check_circle) { - position: absolute; -} - -:host + ::ng-deep app-callout { - top: 60px; -} - -@media (width < 742px) { - :host + ::ng-deep app-callout { - top: 80px; - } -} +:host .info-pilot ::ng-deep app-program-type-icon { + padding-right: 1px; -@media (width < 490px) { - :host + ::ng-deep app-callout { - top: 100px; + .icon { + width: 18px; + height: 18px; + line-height: 18px; } } @@ -61,10 +49,16 @@ .callout-container.info { margin: 24px 32px; - background-color: mat.get-color-from-palette($color-primary, 50); + background-color: mat.m2-get-color-from-palette($color-primary, 50); .callout-icon { - color: mat.get-color-from-palette($color-primary, 700); + color: mat.m2-get-color-from-palette($color-primary, 700); + } + + .info-pilot { + width: 24px; + display: flex; + justify-content: center; } } @@ -117,3 +111,9 @@ line-height: 20px; letter-spacing: 0.2px; } + +.callout-close-button { + margin-left: auto; + margin-right: -20px; + color: $warn; +} diff --git a/modules/ui/src/app/components/callout/callout.component.spec.ts b/modules/ui/src/app/components/callout/callout.component.spec.ts index 28215ec7c..fab52c949 100644 --- a/modules/ui/src/app/components/callout/callout.component.spec.ts +++ b/modules/ui/src/app/components/callout/callout.component.spec.ts @@ -16,6 +16,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CalloutComponent } from './callout.component'; +import { MatIconTestingModule } from '@angular/material/icon/testing'; describe('CalloutComponent', () => { let component: CalloutComponent; @@ -24,7 +25,7 @@ describe('CalloutComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [CalloutComponent], + imports: [CalloutComponent, MatIconTestingModule], }).compileComponents(); fixture = TestBed.createComponent(CalloutComponent); component = fixture.componentInstance; @@ -42,4 +43,27 @@ describe('CalloutComponent', () => { expect(calloutContainerdEl?.classList).toContain('mockValue'); }); + + describe('closeable', () => { + beforeEach(() => { + fixture.componentRef.setInput('closable', true); + fixture.detectChanges(); + }); + + it('should have close button', () => { + const closeButton = compiled.querySelector('.callout-close-button'); + + expect(closeButton).toBeTruthy(); + }); + + it('should emit event', () => { + const calloutClosedSpy = spyOn(component.calloutClosed, 'emit'); + const closeButton = compiled.querySelector( + '.callout-close-button' + ) as HTMLButtonElement; + closeButton?.click(); + + expect(calloutClosedSpy).toHaveBeenCalled(); + }); + }); }); diff --git a/modules/ui/src/app/components/callout/callout.component.ts b/modules/ui/src/app/components/callout/callout.component.ts index bfb6ea9bf..8fa46d9f0 100644 --- a/modules/ui/src/app/components/callout/callout.component.ts +++ b/modules/ui/src/app/components/callout/callout.component.ts @@ -13,18 +13,38 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + Output, +} from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatIconModule } from '@angular/material/icon'; +import { MatButtonModule } from '@angular/material/button'; +import { CalloutType } from '../../model/callout-type'; +import { ProgramType } from '../../model/program-type'; +import { ProgramTypeIconComponent } from '../program-type-icon/program-type-icon.component'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + CommonModule, + MatIconModule, + MatButtonModule, + ProgramTypeIconComponent, + ], selector: 'app-callout', standalone: true, - imports: [CommonModule, MatIconModule], - templateUrl: './callout.component.html', styleUrls: ['./callout.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './callout.component.html', }) export class CalloutComponent { + readonly CalloutType = CalloutType; + readonly ProgramType = ProgramType; + @Input() id: string | null = null; @Input() type = ''; + @Input() closable = false; + @Output() calloutClosed = new EventEmitter(); } diff --git a/modules/ui/src/app/components/component-with-announcement.ts b/modules/ui/src/app/components/component-with-announcement.ts new file mode 100644 index 000000000..0f555e15a --- /dev/null +++ b/modules/ui/src/app/components/component-with-announcement.ts @@ -0,0 +1,35 @@ +import { LiveAnnouncer } from '@angular/cdk/a11y'; +import { FocusManagerService } from '../services/focus-manager.service'; +import { timer } from 'rxjs'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Constructor = new (...args: any[]) => T; + +export function ComponentWithAnnouncement(base: T) { + return class extends base { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(...args: any[]) { + const [dialogRef, title, liveAnnouncer, focusService] = args; + super(...args); + + this.focusService = focusService; + + dialogRef.afterOpened().subscribe(() => { + (liveAnnouncer as LiveAnnouncer).clear(); + (liveAnnouncer as LiveAnnouncer) + .announce(title, 'assertive') + .then(() => { + timer(200).subscribe(() => { + this.focusService.focusFirstElementInContainer(); + }); + }); + }); + + dialogRef.beforeClosed().subscribe(() => { + (liveAnnouncer as LiveAnnouncer).clear(); + }); + } + + focusService!: FocusManagerService; + }; +} diff --git a/modules/ui/src/app/components/device-item/device-item.component.html b/modules/ui/src/app/components/device-item/device-item.component.html index 8f85b6edf..f1586f9a0 100644 --- a/modules/ui/src/app/components/device-item/device-item.component.html +++ b/modules/ui/src/app/components/device-item/device-item.component.html @@ -14,26 +14,46 @@ limitations under the License. --> + +
+ +
+ + +

+ {{ device.test_pack }} + + + {{ device.manufacturer }} +

+

{{ device.model }} -

-
+

+

{{ device.mac_addr }} -

- +

+
- + + + + +
+ + +
diff --git a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.scss b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.scss index 0a92617c1..dd937047a 100644 --- a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.scss +++ b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.scss @@ -13,7 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@use '@angular/material' as mat; @import '../../../theming/colors'; +@import '../../../theming/variables'; :host { display: grid; @@ -31,6 +33,7 @@ } .risk-profile-select-form-content { + margin: 14px 0 6px; font-family: Roboto, sans-serif; font-size: 14px; line-height: 20px; @@ -49,8 +52,14 @@ } .risk-profile-select-form-actions { + justify-content: flex-end; min-height: 30px; padding: 16px 0 0; + gap: 8px; + + &:has(app-download-report) { + justify-content: space-between; + } } .profile-select { @@ -84,3 +93,81 @@ align-self: center; margin-right: 16px; } +.testing-result-heading { + margin: 16px 0; +} +.testing-result-title { + margin: 0; + font-size: 32px; + line-height: 40px; + text-align: center; + color: $grey-900; +} + +.testing-result-subtitle { + margin: 0; + font-family: $font-secondary; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.2px; + text-align: center; + color: $grey-800; +} + +.testing-result { + display: flex; + height: auto; + min-height: 176px; + align-items: center; + gap: 8px; + margin: 6px 0 10px; + border-radius: 8px; +} + +.testing-result-status { + display: flex; + justify-content: center; + align-items: center; + flex: 1 0 0; + min-width: 208px; + width: fit-content; + height: 100%; + min-height: 176px; + box-sizing: border-box; + margin: 0; + padding: 16px; + border-radius: 8px; + color: $white; + font-size: 24px; + line-height: 32px; + background: red; +} + +.testing-result-description { + display: flex; + justify-content: center; + box-sizing: border-box; + margin: 0; + padding: 8px 24px; + color: $grey-800; + font-family: $font-secondary; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.2px; +} + +.failed-result { + background: $red-50; + + .testing-result-status { + background: $red-800; + } +} + +.success-result { + background: $green-50; + + .testing-result-status { + background: mat.m2-get-color-from-palette($color-accent, 700); + } +} diff --git a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts index b042bdabf..a703edb6d 100644 --- a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts +++ b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts @@ -19,10 +19,13 @@ import { MatOptionModule } from '@angular/material/core'; import { TestRunService } from '../../services/test-run.service'; import { Routes } from '../../model/routes'; import { RouterLink } from '@angular/router'; -import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip'; +import { TestrunStatus, StatusOfTestrun } from '../../model/testrun-status'; +import { DownloadReportComponent } from '../download-report/download-report.component'; interface DialogData { profiles: Profile[]; + testrunStatus?: TestrunStatus; + isTestingComplete?: boolean; } @Component({ @@ -39,8 +42,7 @@ interface DialogData { MatSelectModule, MatOptionModule, RouterLink, - MatTooltip, - MatTooltipModule, + DownloadReportComponent, ], templateUrl: './download-zip-modal.component.html', styleUrl: './download-zip-modal.component.scss', @@ -52,6 +54,7 @@ export class DownloadZipModalComponent extends EscapableDialogComponent { questions: [], } as Profile; public readonly Routes = Routes; + public readonly StatusOfTestrun = StatusOfTestrun; profiles: Profile[] = []; selectedProfile: Profile; constructor( @@ -75,6 +78,7 @@ export class DownloadZipModalComponent extends EscapableDialogComponent { cancel(profile?: Profile | null) { if (profile === null) { this.dialogRef.close(null); + return; } let value = profile?.name; if (profile && profile?.name === this.NO_PROFILE.name) { diff --git a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.html b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.html new file mode 100644 index 000000000..0ea7d3df4 --- /dev/null +++ b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.html @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ description }} + + Please, check. “ and \ are not allowed. + + + The field is required + + + The field must be a maximum of + {{ getControl(formControlName).getError('maxlength').requiredLength }} + characters. + + + + + + + + {{ description }} + + Please, check. “ and \ are not allowed. + + + The field is required + + + The field must be a maximum of + {{ getControl(formControlName).getError('maxlength').requiredLength }} + characters. + + + + + + + + {{ description }} + + The field is required + + + Please, check the email address. Valid e-mail can contain only latin + letters, numbers, @ and . (dot). + + + The field must be a maximum of + {{ getControl(formControlName).getError('maxlength').requiredLength }} + characters. + + + + + +
+

+ + {{ getOptionValue(option) }} + +

+ {{ + description + }} +
+
+ + + + + + {{ getOptionValue(option) }} + + + {{ + description + }} + + + The field is required + + + + diff --git a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.scss b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.scss new file mode 100644 index 000000000..14f7086f0 --- /dev/null +++ b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.scss @@ -0,0 +1,67 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@use '@angular/material' as mat; +@import 'src/theming/colors'; +@import 'src/theming/variables'; + +::ng-deep .field-label { + margin: 0; + color: $grey-800; + font-size: 18px; + line-height: 24px; + padding-top: 24px; + padding-bottom: 16px; + display: inline-block; + &:has(+ .field-select-multiple.ng-invalid.ng-dirty) { + color: mat.m2-get-color-from-palette($color-warn, 700); + } +} +mat-form-field { + width: 100%; +} +.field-hint { + font-family: $font-secondary; + font-size: 12px; + font-weight: 400; + line-height: 16px; + text-align: left; + padding-top: 8px; +} + +.form-field { + width: 100%; +} + +.form-field ::ng-deep .mat-mdc-form-field-textarea-control { + display: inherit; +} + +.field-select-multiple { + .field-select-checkbox { + &:has(::ng-deep .mat-mdc-checkbox-checked) { + background: mat.m2-get-color-from-palette($color-primary, 50); + } + ::ng-deep .mdc-checkbox__ripple { + display: none; + } + &:first-of-type { + margin-top: 0; + } + &:last-of-type { + margin-bottom: 8px; + } + } +} diff --git a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.spec.ts b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.spec.ts new file mode 100644 index 000000000..ef63d45d7 --- /dev/null +++ b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.spec.ts @@ -0,0 +1,235 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DynamicFormComponent } from './dynamic-form.component'; +import { Component, ViewChild, ViewEncapsulation } from '@angular/core'; +import { + FormBuilder, + FormGroup, + FormsModule, + ReactiveFormsModule, +} from '@angular/forms'; +import { PROFILE_FORM } from '../../mocks/profile.mock'; +import { FormControlType } from '../../model/question'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; + +@Component({ + template: + '
', +}) +class DummyComponent { + @ViewChild('dynamicForm') public dynamicForm!: DynamicFormComponent; + public testForm!: FormGroup; + public format = PROFILE_FORM; + constructor(private readonly fb: FormBuilder) { + this.testForm = this.fb.group({ + test: [''], + }); + } +} + +describe('DynamicFormComponent', () => { + let dummy: DummyComponent; + let fixture: ComponentFixture; + let compiled: HTMLElement; + let component: DynamicFormComponent; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [DummyComponent], + imports: [ + DynamicFormComponent, + ReactiveFormsModule, + FormsModule, + NoopAnimationsModule, + ], + }) + .overrideComponent(DummyComponent, { + set: { encapsulation: ViewEncapsulation.None }, + }) + .compileComponents(); + + fixture = TestBed.createComponent(DummyComponent); + + dummy = fixture.componentInstance; + compiled = fixture.nativeElement as HTMLElement; + fixture.detectChanges(); + component = dummy.dynamicForm; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + PROFILE_FORM.forEach((item, index) => { + it(`should have form field with specific type"`, () => { + const fields = compiled.querySelectorAll('.form-field'); + + if (item.type === FormControlType.SELECT) { + const select = fields[index].querySelector('mat-select'); + expect(select).toBeTruthy(); + } else if (item.type === FormControlType.SELECT_MULTIPLE) { + const select = fields[index].querySelector('mat-checkbox'); + expect(select).toBeTruthy(); + } else if (item.type === FormControlType.TEXTAREA) { + const input = fields[index]?.querySelector('textarea'); + expect(input).toBeTruthy(); + } else { + const input = fields[index]?.querySelector('input'); + expect(input).toBeTruthy(); + } + }); + + it('should have label', () => { + const labels = compiled.querySelectorAll('.field-label'); + + const label = item.question; + expect(labels[index].textContent?.trim()).toEqual(label); + }); + + it('should have hint', () => { + const fields = compiled.querySelectorAll('.form-field'); + const hint = fields[index].querySelector('mat-hint'); + + if (item.description) { + expect(hint?.textContent?.trim()).toEqual(item.description); + } else { + expect(hint).toBeNull(); + } + }); + + if (item.type === FormControlType.SELECT) { + describe('select', () => { + it(`should have default value if provided`, () => { + const fields = compiled.querySelectorAll('.form-field'); + const select = fields[index].querySelector('mat-select'); + expect(select?.textContent?.trim()).toEqual(item.default || ''); + }); + + it('should have "required" error when field is not filled', () => { + const fields = compiled.querySelectorAll('.form-field'); + + component.getControl(index).setValue(''); + component.getControl(index).markAsTouched(); + + fixture.detectChanges(); + + const error = fields[index].querySelector('mat-error')?.innerHTML; + + expect(error).toContain('The field is required'); + }); + }); + } + + if (item.type === FormControlType.SELECT_MULTIPLE) { + describe('select multiple', () => { + it(`should mark form group as dirty while tab navigation`, () => { + const fields = compiled.querySelectorAll('.form-field'); + const checkbox = fields[index].querySelector( + '.field-select-checkbox:last-of-type mat-checkbox' + ); + checkbox?.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' })); + fixture.detectChanges(); + + expect(component.getControl(index).dirty).toBeTrue(); + }); + }); + } + + if ( + item.type === FormControlType.TEXT || + item.type === FormControlType.TEXTAREA || + item.type === FormControlType.EMAIL_MULTIPLE + ) { + describe('text or text-long or email-multiple', () => { + if (item.validation?.required) { + it('should have "required" error when field is not filled', () => { + const fields = compiled.querySelectorAll('.form-field'); + const input = fields[index].querySelector( + '.mat-mdc-input-element' + ) as HTMLInputElement; + ['', ' '].forEach(value => { + input.value = value; + input.dispatchEvent(new Event('input')); + component.getControl(index).markAsTouched(); + fixture.detectChanges(); + const errors = fields[index].querySelectorAll('mat-error'); + let hasError = false; + errors.forEach(error => { + if (error.textContent === 'The field is required') { + hasError = true; + } + }); + + expect(hasError).toBeTrue(); + }); + }); + } + + it('should have "invalid_format" error when field does not satisfy validation rules', () => { + const fields = compiled.querySelectorAll('.form-field'); + const input: HTMLInputElement = fields[index].querySelector( + '.mat-mdc-input-element' + ) as HTMLInputElement; + input.value = 'as\\\\\\\\\\""""""""'; + input.dispatchEvent(new Event('input')); + component.getControl(index).markAsTouched(); + fixture.detectChanges(); + const result = + item.type === FormControlType.EMAIL_MULTIPLE + ? 'Please, check the email address. Valid e-mail can contain only latin letters, numbers, @ and . (dot).' + : 'Please, check. “ and \\ are not allowed.'; + const errors = fields[index].querySelectorAll('mat-error'); + let hasError = false; + errors.forEach(error => { + if (error.textContent === result) { + hasError = true; + } + }); + + expect(hasError).toBeTrue(); + }); + + if (item.validation?.max) { + it('should have "maxlength" error when field is exceeding max length', () => { + const fields = compiled.querySelectorAll('.form-field'); + const input: HTMLInputElement = fields[index].querySelector( + '.mat-mdc-input-element' + ) as HTMLInputElement; + input.value = + 'very long value very long value very long value very long value very long value very long value very long value very long value very long value very long value'; + input.dispatchEvent(new Event('input')); + component.getControl(index).markAsTouched(); + fixture.detectChanges(); + + const errors = fields[index].querySelectorAll('mat-error'); + let hasError = false; + errors.forEach(error => { + if ( + error.textContent === + `The field must be a maximum of ${item.validation?.max} characters.` + ) { + hasError = true; + } + }); + expect(hasError).toBeTrue(); + }); + } + }); + } + }); +}); diff --git a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.ts b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.ts new file mode 100644 index 000000000..3d6b13016 --- /dev/null +++ b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.ts @@ -0,0 +1,171 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Component, inject, Input, OnInit } from '@angular/core'; +import { + FormControlType, + OptionType, + QuestionFormat, + Validation, +} from '../../model/question'; +import { + AbstractControl, + ControlContainer, + FormBuilder, + FormControl, + FormGroup, + ReactiveFormsModule, + ValidatorFn, + Validators, +} from '@angular/forms'; +import { + MatError, + MatFormField, + MatOption, + MatSelectModule, +} from '@angular/material/select'; +import { MatButtonModule } from '@angular/material/button'; +import { CommonModule } from '@angular/common'; +import { MatInputModule } from '@angular/material/input'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { TextFieldModule } from '@angular/cdk/text-field'; +import { DeviceValidators } from '../../pages/devices/components/device-form/device.validators'; +import { ProfileValidators } from '../../pages/risk-assessment/profile-form/profile.validators'; +@Component({ + selector: 'app-dynamic-form', + standalone: true, + imports: [ + MatFormField, + MatOption, + MatButtonModule, + CommonModule, + ReactiveFormsModule, + MatInputModule, + MatError, + MatFormFieldModule, + MatSelectModule, + MatCheckboxModule, + TextFieldModule, + ], + viewProviders: [ + { + provide: ControlContainer, + useFactory: () => inject(ControlContainer, { skipSelf: true }), + }, + ], + templateUrl: './dynamic-form.component.html', + styleUrl: './dynamic-form.component.scss', +}) +export class DynamicFormComponent implements OnInit { + public readonly FormControlType = FormControlType; + + @Input() format: QuestionFormat[] = []; + @Input() optionKey: string | undefined; + + parentContainer = inject(ControlContainer); + get formGroup() { + return this.parentContainer.control as FormGroup; + } + + constructor( + private fb: FormBuilder, + private deviceValidators: DeviceValidators, + private profileValidators: ProfileValidators + ) {} + getControl(name: string | number) { + return this.formGroup.get(name.toString()) as AbstractControl; + } + + getFormGroup(name: string | number): FormGroup { + return this.formGroup?.controls[name] as FormGroup; + } + + public markSectionAsDirty( + optionIndex: number, + optionLength: number, + formControlName: string + ) { + if (optionIndex === optionLength - 1) { + this.getControl(formControlName).markAsDirty({ + onlySelf: true, + }); + } + } + + ngOnInit() { + this.createProfileForm(this.format); + } + + createProfileForm(questions: QuestionFormat[]) { + questions.forEach((question, index) => { + if (question.type === FormControlType.SELECT_MULTIPLE) { + this.formGroup.addControl( + index.toString(), + this.getMultiSelectGroup(question) + ); + } else { + const validators = this.getValidators( + question.type, + question.validation + ); + this.formGroup.addControl( + index.toString(), + new FormControl(question.default || '', validators) + ); + } + }); + } + + getValidators(type: FormControlType, validation?: Validation): ValidatorFn[] { + const validators: ValidatorFn[] = []; + if (validation) { + if (validation.required) { + validators.push(this.profileValidators.textRequired()); + } + if (validation.max) { + validators.push(Validators.maxLength(Number(validation.max))); + } + if (type === FormControlType.EMAIL_MULTIPLE) { + validators.push(this.profileValidators.emailStringFormat()); + } + if (type === FormControlType.TEXT || type === FormControlType.TEXTAREA) { + validators.push(this.profileValidators.textFormat()); + } + } + return validators; + } + + getMultiSelectGroup(question: QuestionFormat): FormGroup { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const group: any = {}; + question.options?.forEach((option, index) => { + group[index] = false; + }); + return this.fb.group(group, { + validators: question.validation?.required + ? [this.profileValidators.multiSelectRequired] + : [], + }); + } + + getOptionValue(option: OptionType) { + if (this.optionKey && typeof option === 'object') { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (option as any)[this.optionKey]; + } + return option; + } +} diff --git a/modules/ui/src/app/components/program-type-icon/program-type-con.component.spec.ts b/modules/ui/src/app/components/program-type-icon/program-type-con.component.spec.ts new file mode 100644 index 000000000..7a984eff0 --- /dev/null +++ b/modules/ui/src/app/components/program-type-icon/program-type-con.component.spec.ts @@ -0,0 +1,46 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProgramTypeIconComponent } from './program-type-icon.component'; +import { MatIconTestingModule } from '@angular/material/icon/testing'; + +describe('ProgramTypeIconComponent', () => { + let component: ProgramTypeIconComponent; + let fixture: ComponentFixture; + let compiled: HTMLElement; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ProgramTypeIconComponent, MatIconTestingModule], + }).compileComponents(); + fixture = TestBed.createComponent(ProgramTypeIconComponent); + component = fixture.componentInstance; + component.type = 'pilot'; + compiled = fixture.nativeElement as HTMLElement; + fixture.detectChanges(); + }); + + it('should create component', () => { + expect(component).toBeTruthy(); + }); + + it('should have svgIcon provided from type', () => { + const iconEl = compiled.querySelector('.icon'); + + expect(iconEl?.getAttribute('ng-reflect-svg-icon')).toEqual('pilot'); + }); +}); diff --git a/modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts b/modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts new file mode 100644 index 000000000..ccf5b3335 --- /dev/null +++ b/modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts @@ -0,0 +1,39 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Component, Input } from '@angular/core'; +import { MatIcon } from '@angular/material/icon'; + +@Component({ + selector: 'app-program-type-icon', + standalone: true, + imports: [MatIcon], + template: ` `, + styles: ` + :host { + display: inline-flex; + align-items: center; + padding-right: 4px; + } + .icon { + width: 16px; + height: 16px; + line-height: 16px; + } + `, +}) +export class ProgramTypeIconComponent { + @Input() type = ''; +} diff --git a/modules/ui/src/app/components/report-action/report-action.component.ts b/modules/ui/src/app/components/report-action/report-action.component.ts index 62dc39d58..debf132fa 100644 --- a/modules/ui/src/app/components/report-action/report-action.component.ts +++ b/modules/ui/src/app/components/report-action/report-action.component.ts @@ -15,6 +15,9 @@ export class ReportActionComponent { constructor(private datePipe: DatePipe) {} getTestRunId(data: TestrunStatus) { + if (!data.device) { + return ''; + } return `${data.device.manufacturer} ${data.device.model} ${ data.device.firmware } ${this.getFormattedDateString(data.started)}`; diff --git a/modules/ui/src/app/components/simple-dialog/simple-dialog.component.html b/modules/ui/src/app/components/simple-dialog/simple-dialog.component.html index 6d5f768d6..975773924 100644 --- a/modules/ui/src/app/components/simple-dialog/simple-dialog.component.html +++ b/modules/ui/src/app/components/simple-dialog/simple-dialog.component.html @@ -20,6 +20,8 @@ +
+
+
+ + + diff --git a/modules/ui/src/app/components/stepper/stepper.component.scss b/modules/ui/src/app/components/stepper/stepper.component.scss new file mode 100644 index 000000000..d5be19c55 --- /dev/null +++ b/modules/ui/src/app/components/stepper/stepper.component.scss @@ -0,0 +1,91 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import '../../../theming/colors'; +@import '../../../theming/variables'; + +.form-container { + height: 100%; + display: flex; + flex-direction: column; +} + +.form-header { + padding: 24px; +} + +.form-content { + display: grid; + padding: 24px 8px; + gap: 10px; + overflow: hidden; + height: 100%; +} + +.form-footer { + margin-top: auto; + display: inline-block; + text-align: center; + height: 24px; + padding: 0 24px 24px 24px; +} + +.form-steps { + display: inline-flex; + text-align: center; + align-items: center; + height: 100%; +} + +.form-step { + border: 2px solid $lighter-grey; + width: 4px; + height: 4px; + display: inline-block; + border-radius: 100%; + margin: 0 8px; + &.step-active { + border-color: $secondary; + background: $secondary; + } +} + +.form-button-back { + float: left; +} + +.form-button-forward { + float: right; +} + +.form-button-back, +.form-button-forward { + height: $icon-size; + width: $icon-size; + min-width: $icon-size; + margin: 0; + padding: 0; + & mat-icon { + color: $secondary; + width: $icon-size; + height: $icon-size; + font-size: $icon-size; + margin: 0; + } + + &.hidden { + visibility: hidden; + } +} diff --git a/modules/ui/src/app/components/stepper/stepper.component.spec.ts b/modules/ui/src/app/components/stepper/stepper.component.spec.ts new file mode 100644 index 000000000..25b5f9740 --- /dev/null +++ b/modules/ui/src/app/components/stepper/stepper.component.spec.ts @@ -0,0 +1,101 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { StepperComponent } from './stepper.component'; +import { Component, ViewChild, ViewEncapsulation } from '@angular/core'; +import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { CdkStep } from '@angular/cdk/stepper'; +import { MatFormField, MatFormFieldModule } from '@angular/material/form-field'; +import { CommonModule } from '@angular/common'; +import { MatInputModule } from '@angular/material/input'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; + +@Component({ + selector: 'app-stepper-bypass', + standalone: true, + imports: [ + CdkStep, + StepperComponent, + MatFormField, + CommonModule, + ReactiveFormsModule, + MatInputModule, + MatFormFieldModule, + ], + templateUrl: './stepper-test.component.html', +}) +class TestStepperComponent { + @ViewChild('stepper') public stepper!: StepperComponent; + testForm; + firstStep; + secondStep; + constructor(private fb: FormBuilder) { + this.firstStep = this.fb.group({ + firstControl: ['', [Validators.required]], + }); + this.secondStep = this.fb.group({ + secondControl: ['', [Validators.required]], + }); + this.testForm = this.fb.group({ + steps: this.fb.array([this.firstStep, this.secondStep]), + }); + } +} + +describe('StepperComponent', () => { + let component: TestStepperComponent; + let fixture: ComponentFixture; + let compiled: HTMLElement; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [StepperComponent, TestStepperComponent, NoopAnimationsModule], + }) + .overrideComponent(TestStepperComponent, { + set: { encapsulation: ViewEncapsulation.None }, + }) + .compileComponents(); + + fixture = TestBed.createComponent(TestStepperComponent); + component = fixture.componentInstance; + compiled = fixture.nativeElement as HTMLElement; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should have title', () => { + expect(fixture.nativeElement.querySelector('.form-header')).toBeTruthy(); + }); + + it('should not mark selected step touched if not interacted', () => { + component.stepper.nextClick(); + + expect(component.firstStep.touched).toBeFalse(); + }); + + it('should mark selected step touched if interacted', () => { + const button = compiled.querySelector( + '.form-button-forward' + ) as HTMLButtonElement; + button.click(); + + expect(component.firstStep.touched).toBeTrue(); + }); +}); diff --git a/modules/ui/src/app/components/stepper/stepper.component.ts b/modules/ui/src/app/components/stepper/stepper.component.ts new file mode 100644 index 000000000..ee7f1c5ab --- /dev/null +++ b/modules/ui/src/app/components/stepper/stepper.component.ts @@ -0,0 +1,72 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Component, Input, TemplateRef } from '@angular/core'; +import { CdkStepper, CdkStepperModule } from '@angular/cdk/stepper'; +import { NgForOf, NgIf, NgTemplateOutlet } from '@angular/common'; +import { MatIcon } from '@angular/material/icon'; +import { MatButton, MatIconButton } from '@angular/material/button'; +import { FormGroup } from '@angular/forms'; +import { MatTooltipModule } from '@angular/material/tooltip'; + +@Component({ + selector: 'app-stepper', + standalone: true, + imports: [ + NgForOf, + NgTemplateOutlet, + CdkStepperModule, + NgIf, + MatIcon, + MatIconButton, + MatButton, + MatTooltipModule, + ], + templateUrl: './stepper.component.html', + styleUrl: './stepper.component.scss', + providers: [{ provide: CdkStepper, useExisting: StepperComponent }], +}) +export class StepperComponent extends CdkStepper { + @Input() header: TemplateRef | undefined; + @Input() title = ''; + @Input() activeClass = 'active'; + + forwardButtonHidden() { + return this.selectedIndex === this.steps.length - 1; + } + + backButtonHidden() { + return this.selectedIndex === 0; + } + + nextClick() { + if ( + this.selected?.interacted && + !(this.selected?.stepControl as FormGroup)?.valid + ) { + this.selected?.stepControl?.markAllAsTouched(); + } + } + + getStepLabel(isActive: boolean) { + return isActive + ? `Step #${this.selectedIndex + 1} out of ${this.steps.length}` + : ''; + } + + getNavigationLabel(index: number) { + return `Go to step #${index} out of ${this.title}`; + } +} diff --git a/modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts b/modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts new file mode 100644 index 000000000..30c3108e6 --- /dev/null +++ b/modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts @@ -0,0 +1,91 @@ +import { + ComponentFixture, + fakeAsync, + TestBed, + tick, +} from '@angular/core/testing'; + +import { TestingCompleteComponent } from './testing-complete.component'; +import { TestRunService } from '../../services/test-run.service'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { Router } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { Component } from '@angular/core'; +import { MOCK_PROGRESS_DATA_COMPLIANT } from '../../mocks/testrun.mock'; +import { of } from 'rxjs'; +import { MatDialogRef } from '@angular/material/dialog'; +import { DownloadZipModalComponent } from '../download-zip-modal/download-zip-modal.component'; +import { Routes } from '../../model/routes'; + +describe('TestingCompleteComponent', () => { + let component: TestingCompleteComponent; + let fixture: ComponentFixture; + let router: Router; + + const testrunServiceMock: jasmine.SpyObj = + jasmine.createSpyObj('testrunServiceMock', ['downloadZip']); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + RouterTestingModule.withRoutes([ + { path: 'risk-assessment', component: FakeRiskAssessmentComponent }, + ]), + TestingCompleteComponent, + BrowserAnimationsModule, + ], + providers: [{ provide: TestRunService, useValue: testrunServiceMock }], + }).compileComponents(); + + fixture = TestBed.createComponent(TestingCompleteComponent); + component = fixture.componentInstance; + router = TestBed.get(Router); + component.data = MOCK_PROGRESS_DATA_COMPLIANT; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('#onInit', () => { + beforeEach(() => { + testrunServiceMock.downloadZip.calls.reset(); + }); + + it('should call downloadZip on service if profile is a string', fakeAsync(() => { + const openSpy = spyOn(component.dialog, 'open').and.returnValue({ + afterClosed: () => of(''), + } as MatDialogRef); + + component.ngOnInit(); + tick(1000); + + expect(openSpy).toHaveBeenCalledWith(DownloadZipModalComponent, { + ariaLabel: 'Testing complete', + data: { + profiles: [], + testrunStatus: MOCK_PROGRESS_DATA_COMPLIANT, + isTestingComplete: true, + }, + autoFocus: 'first-tabbable', + ariaDescribedBy: 'testing-result-main-info', + hasBackdrop: true, + disableClose: true, + panelClass: 'initiate-test-run-dialog', + }); + + tick(); + + expect(testrunServiceMock.downloadZip).toHaveBeenCalled(); + expect(router.url).not.toBe(Routes.RiskAssessment); + openSpy.calls.reset(); + })); + }); +}); + +@Component({ + selector: 'app-fake-risk-assessment-component', + template: '', +}) +class FakeRiskAssessmentComponent {} diff --git a/modules/ui/src/app/components/testing-complete/testing-complete.component.ts b/modules/ui/src/app/components/testing-complete/testing-complete.component.ts new file mode 100644 index 000000000..28da57bee --- /dev/null +++ b/modules/ui/src/app/components/testing-complete/testing-complete.component.ts @@ -0,0 +1,99 @@ +import { + ChangeDetectionStrategy, + Component, + Input, + OnDestroy, + OnInit, +} from '@angular/core'; +import { Subject, takeUntil, timer } from 'rxjs'; +import { MatDialog } from '@angular/material/dialog'; +import { TestRunService } from '../../services/test-run.service'; +import { Router } from '@angular/router'; +import { DownloadZipModalComponent } from '../download-zip-modal/download-zip-modal.component'; +import { Routes } from '../../model/routes'; +import { Profile } from '../../model/profile'; +import { TestrunStatus } from '../../model/testrun-status'; +import { FocusManagerService } from '../../services/focus-manager.service'; + +@Component({ + selector: 'app-testing-complete', + standalone: true, + imports: [], + template: '', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TestingCompleteComponent implements OnDestroy, OnInit { + @Input() profiles: Profile[] = []; + @Input() data!: TestrunStatus | null; + private destroy$: Subject = new Subject(); + + constructor( + public dialog: MatDialog, + private testrunService: TestRunService, + private route: Router, + private focusManagerService: FocusManagerService + ) {} + + ngOnInit() { + timer(1000).subscribe(() => { + this.openTestingCompleteModal(); + }); + } + ngOnDestroy() { + this.destroy$.next(true); + this.destroy$.unsubscribe(); + } + + private openTestingCompleteModal(): void { + const dialogRef = this.dialog.open(DownloadZipModalComponent, { + ariaLabel: 'Testing complete', + data: { + profiles: this.profiles, + testrunStatus: this.data, + isTestingComplete: true, + }, + autoFocus: 'first-tabbable', + ariaDescribedBy: 'testing-result-main-info', + hasBackdrop: true, + disableClose: true, + panelClass: 'initiate-test-run-dialog', + }); + + dialogRef + ?.afterClosed() + .pipe(takeUntil(this.destroy$)) + .subscribe(profile => { + if (profile === undefined) { + // close modal + this.focusFirstElement(); + return; + } + if (profile === null) { + this.navigateToRiskAssessment(); + } else if (this.data?.report != null) { + this.testrunService.downloadZip( + this.getZipLink(this.data?.report), + profile + ); + } + }); + } + + private navigateToRiskAssessment(): void { + this.route.navigate([Routes.RiskAssessment]).then(() => { + this.focusFirstElement(); + }); + } + + private focusFirstElement() { + timer(1000) + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + this.focusManagerService.focusFirstElementInContainer(); + }); + } + + private getZipLink(reportURL: string): string { + return reportURL.replace('report', 'export'); + } +} diff --git a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.html b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.html index 516e72b49..6867dd445 100644 --- a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.html +++ b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.html @@ -81,24 +81,12 @@

Welcome to Testrun!

-
- - Risk Assessment feature added! -

- Now you can answer a short questionnaire, create a security profile and - attach it to the Testrun result to complete a device verification. Also, - it will speed up the process a lot! -

-

- +

+ + Pilot Assessment +

+ Pilot project support is now offered through Testrun. Follow the + instructions set out to get your pilot recommendation.

diff --git a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.scss b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.scss index 9eb334496..c32f47a41 100644 --- a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.scss +++ b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.scss @@ -81,6 +81,17 @@ } } +.section-container-pilot { + .section-title { + font-weight: 500; + letter-spacing: 0.25px; + } + .section-content { + margin: 0; + padding-top: 9px; + } +} + .consent-actions { border-top: 1px solid $lighter-grey; margin: 0 -16px; diff --git a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.spec.ts b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.spec.ts index c538554f2..d6925c131 100644 --- a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.spec.ts +++ b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.spec.ts @@ -13,7 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { + ComponentFixture, + fakeAsync, + TestBed, + tick, +} from '@angular/core/testing'; import { ConsentDialogComponent } from './consent-dialog.component'; import { @@ -24,15 +29,28 @@ import { import { MatButtonModule } from '@angular/material/button'; import { of } from 'rxjs'; import { NEW_VERSION, VERSION } from '../../../mocks/version.mock'; +import { MatIconTestingModule } from '@angular/material/icon/testing'; +import { FocusManagerService } from '../../../services/focus-manager.service'; +import SpyObj = jasmine.SpyObj; describe('ConsentDialogComponent', () => { let component: ConsentDialogComponent; let fixture: ComponentFixture; let compiled: HTMLElement; + let mockFocusManagerService: SpyObj; beforeEach(() => { + mockFocusManagerService = jasmine.createSpyObj('mockFocusManagerService', [ + 'focusFirstElementInContainer', + ]); + TestBed.configureTestingModule({ - imports: [ConsentDialogComponent, MatDialogModule, MatButtonModule], + imports: [ + ConsentDialogComponent, + MatDialogModule, + MatButtonModule, + MatIconTestingModule, + ], providers: [ { provide: MatDialogRef, @@ -42,11 +60,12 @@ describe('ConsentDialogComponent', () => { }, }, { provide: MAT_DIALOG_DATA, useValue: {} }, + { provide: FocusManagerService, useValue: mockFocusManagerService }, ], }); fixture = TestBed.createComponent(ConsentDialogComponent); component = fixture.componentInstance; - component.data = { version: NEW_VERSION, hasRiskProfiles: false }; + component.data = { version: NEW_VERSION }; component.optOut = false; fixture.detectChanges(); compiled = fixture.nativeElement as HTMLElement; @@ -61,7 +80,7 @@ describe('ConsentDialogComponent', () => { const confirmButton = compiled.querySelector( '.confirm-button' ) as HTMLButtonElement; - const dialogRes = { grant: true, isNavigateToRiskAssessment: undefined }; + const dialogRes = { grant: true }; confirmButton?.click(); @@ -77,7 +96,7 @@ describe('ConsentDialogComponent', () => { const confirmButton = compiled.querySelector( '.confirm-button' ) as HTMLButtonElement; - const dialogRes = { grant: false, isNavigateToRiskAssessment: undefined }; + const dialogRes = { grant: false }; confirmButton?.click(); @@ -97,9 +116,18 @@ describe('ConsentDialogComponent', () => { expect(closeSpy).toHaveBeenCalledTimes(0); }); + it('should set focus to first focusable elem when close dialog', fakeAsync(() => { + component.confirm(true); + tick(100); + + expect( + mockFocusManagerService.focusFirstElementInContainer + ).toHaveBeenCalled(); + })); + describe('with new version available', () => { beforeEach(() => { - component.data = { version: NEW_VERSION, hasRiskProfiles: false }; + component.data = { version: NEW_VERSION }; fixture.detectChanges(); }); @@ -122,7 +150,7 @@ describe('ConsentDialogComponent', () => { describe('with no new version available', () => { beforeEach(() => { - component.data = { version: VERSION, hasRiskProfiles: false }; + component.data = { version: VERSION }; fixture.detectChanges(); }); @@ -134,51 +162,4 @@ describe('ConsentDialogComponent', () => { expect(content).toBeNull(); }); }); - - describe('with no risk assessment profiles', () => { - beforeEach(() => { - component.data = { version: VERSION, hasRiskProfiles: false }; - fixture.detectChanges(); - }); - - it('should has risk-assessment content', () => { - const content = compiled.querySelector( - '.section-content.risk-assessment' - ) as HTMLElement; - - const innerContent = content.innerHTML.trim(); - expect(innerContent).toContain( - 'Now you can answer a short questionnaire' - ); - }); - - it('should close dialog with isNavigateToRiskAssessment as true when click "confirm"', () => { - const closeSpy = spyOn(component.dialogRef, 'close'); - const riskAssessmentBtn = compiled.querySelector( - '.risk-assessment-button' - ) as HTMLButtonElement; - const dialogRes = { grant: true, isNavigateToRiskAssessment: true }; - - riskAssessmentBtn?.click(); - - expect(closeSpy).toHaveBeenCalledWith(dialogRes); - - closeSpy.calls.reset(); - }); - }); - - describe('with risk assessment profiles', () => { - beforeEach(() => { - component.data = { version: VERSION, hasRiskProfiles: true }; - fixture.detectChanges(); - }); - - it('should not has risk-assessment content', () => { - const content = compiled.querySelector( - '.section-content.risk-assessment' - ) as HTMLElement; - - expect(content).toBeNull(); - }); - }); }); diff --git a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.ts b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.ts index 8b27a8961..3becacf89 100644 --- a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.ts +++ b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.ts @@ -26,10 +26,11 @@ import { CalloutType } from '../../../model/callout-type'; import { NgIf } from '@angular/common'; import { MatCheckbox } from '@angular/material/checkbox'; import { FormsModule } from '@angular/forms'; +import { FocusManagerService } from '../../../services/focus-manager.service'; +import { timer } from 'rxjs'; type DialogData = { version: Version; - hasRiskProfiles: boolean; }; @Component({ @@ -50,16 +51,19 @@ export class ConsentDialogComponent { public readonly CalloutType = CalloutType; optOut = false; constructor( + private readonly focusManagerService: FocusManagerService, public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: DialogData ) {} - confirm(optOut: boolean, isNavigateToRiskAssessment?: boolean) { + confirm(optOut: boolean) { // dialog should be closed with opposite value to grant or deny access to GA const dialogResult: ConsentDialogResult = { grant: !optOut, - isNavigateToRiskAssessment, }; this.dialogRef.close(dialogResult); + timer(100).subscribe(() => { + this.focusManagerService.focusFirstElementInContainer(); + }); } } diff --git a/modules/ui/src/app/components/version/version.component.html b/modules/ui/src/app/components/version/version.component.html index d45ea69da..2068f6043 100644 --- a/modules/ui/src/app/components/version/version.component.html +++ b/modules/ui/src/app/components/version/version.component.html @@ -18,7 +18,7 @@ mat-button class="version-content" [class.version-content-update]="version.update_available" - [attr.aria-label]="getVersionButtonLabel(version.installed_version)" + [attr.aria-label]="getVersionButtonLabel(version)" (click)="openConsentDialog(version)"> {{ version?.installed_version }} { let component: VersionComponent; @@ -43,7 +48,7 @@ describe('VersionComponent', () => { mockService = jasmine.createSpyObj(['getVersion', 'fetchVersion']); mockService.getVersion.and.returnValue(versionBehaviorSubject$); TestBed.configureTestingModule({ - imports: [VersionComponent], + imports: [VersionComponent, MatIconTestingModule], providers: [{ provide: TestRunService, useValue: mockService }], }); fixture = TestBed.createComponent(VersionComponent); @@ -56,31 +61,32 @@ describe('VersionComponent', () => { }); it('should get correct aria label for version button', () => { - const labelUnavailableVersion = component.getVersionButtonLabel( - UNAVAILABLE_VERSION.installed_version - ); + const labelUnavailableVersion = + component.getVersionButtonLabel(UNAVAILABLE_VERSION); - const labelAvailableVersion = component.getVersionButtonLabel( - VERSION.installed_version - ); + const labelAvailableVersion = component.getVersionButtonLabel(NEW_VERSION); + + const labelVersion = component.getVersionButtonLabel(VERSION); expect(labelUnavailableVersion).toContain( 'Version temporarily unavailable.' ); expect(labelAvailableVersion).toContain('New version is available.'); + expect(labelVersion).toEqual('v1. Click to open the Welcome modal'); }); - it('should open consent window on start', () => { + it('should open consent window on start', fakeAsync(() => { const openSpy = spyOn(component.dialog, 'open').and.returnValue({ - afterClosed: () => of(true), - } as MatDialogRef); + afterClosed: () => of({ grant: null }), + } as MatDialogRef); versionBehaviorSubject$.next(VERSION); mockService.getVersion.and.returnValue(versionBehaviorSubject$); fixture.detectChanges(); component.ngOnInit(); + tick(2000); expect(openSpy).toHaveBeenCalled(); - }); + })); it('should open consent window when button clicked', () => { const openSpy = spyOn(component.dialog, 'open').and.returnValue({ diff --git a/modules/ui/src/app/components/version/version.component.ts b/modules/ui/src/app/components/version/version.component.ts index 186c3bcf6..ad8fcafa0 100644 --- a/modules/ui/src/app/components/version/version.component.ts +++ b/modules/ui/src/app/components/version/version.component.ts @@ -34,10 +34,10 @@ import { tap } from 'rxjs/internal/operators/tap'; import { Observable } from 'rxjs/internal/Observable'; import { Subject } from 'rxjs/internal/Subject'; import { takeUntil } from 'rxjs/internal/operators/takeUntil'; -import { filter } from 'rxjs'; +import { filter, timer } from 'rxjs'; import { ConsentDialogComponent } from './consent-dialog/consent-dialog.component'; -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type declare const gtag: Function; @Component({ selector: 'app-version', @@ -48,9 +48,7 @@ declare const gtag: Function; }) export class VersionComponent implements OnInit, OnDestroy { @Input() consentShown!: boolean; - @Input() hasRiskProfiles!: boolean; @Output() consentShownEvent = new EventEmitter(); - @Output() navigateToRiskAssessmentEvent = new EventEmitter(); version$!: Observable; private destroy$: Subject = new Subject(); @@ -66,9 +64,10 @@ export class VersionComponent implements OnInit, OnDestroy { filter(version => version !== null), tap(version => { if (!this.consentShown) { - // @ts-expect-error null is filtered - this.openConsentDialog(version); - this.consentShownEvent.emit(); + timer(2000).subscribe(() => { + this.openConsentDialog(version); + this.consentShownEvent.emit(); + }); } // @ts-expect-error data layer is not null window.dataLayer.push({ @@ -79,14 +78,21 @@ export class VersionComponent implements OnInit, OnDestroy { ); } - getVersionButtonLabel(installedVersion: string): string { - return installedVersion === UNAVAILABLE_VERSION.installed_version - ? 'Version temporarily unavailable. Click to open the Welcome modal' - : `${installedVersion} New version is available. Click to update`; + getVersionButtonLabel(installedVersion: Version): string { + if ( + installedVersion.installed_version === + UNAVAILABLE_VERSION.installed_version + ) { + return 'Version temporarily unavailable. Click to open the Welcome modal'; + } + if (installedVersion.update_available) { + return `${installedVersion.installed_version} New version is available. Click to update`; + } + return `${installedVersion.installed_version}. Click to open the Welcome modal`; } openConsentDialog(version: Version) { - const dialogData = { version, hasRiskProfiles: this.hasRiskProfiles }; + const dialogData = { version }; const dialogRef = this.dialog.open(ConsentDialogComponent, { ariaLabel: 'Welcome to Testrun modal window', data: dialogData, @@ -106,10 +112,6 @@ export class VersionComponent implements OnInit, OnDestroy { gtag('consent', 'update', { analytics_storage: dialogResult.grant ? 'granted' : 'denied', }); - - if (dialogResult.isNavigateToRiskAssessment) { - this.navigateToRiskAssessmentEvent.emit(); - } }); } diff --git a/modules/ui/src/app/guards/can-deactivate.guard.spec.ts b/modules/ui/src/app/guards/can-deactivate.guard.spec.ts new file mode 100644 index 000000000..5571654bc --- /dev/null +++ b/modules/ui/src/app/guards/can-deactivate.guard.spec.ts @@ -0,0 +1,31 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { TestBed } from '@angular/core/testing'; + +import { CanDeactivateGuard } from './can-deactivate.guard'; + +describe('CanDeactivateGuard', () => { + let guard: CanDeactivateGuard; + + beforeEach(() => { + TestBed.configureTestingModule({}); + guard = TestBed.inject(CanDeactivateGuard); + }); + + it('should be created', () => { + expect(guard).toBeTruthy(); + }); +}); diff --git a/modules/ui/src/app/guards/can-deactivate.guard.ts b/modules/ui/src/app/guards/can-deactivate.guard.ts new file mode 100644 index 000000000..68fb2147c --- /dev/null +++ b/modules/ui/src/app/guards/can-deactivate.guard.ts @@ -0,0 +1,38 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Injectable } from '@angular/core'; +import { CanDeactivate, UrlTree } from '@angular/router'; +import { Observable } from 'rxjs'; + +type CanDeactivateType = + | Observable + | Promise + | boolean + | UrlTree; + +export interface CanComponentDeactivate { + canDeactivate: () => CanDeactivateType; +} +@Injectable({ + providedIn: 'root', +}) +export class CanDeactivateGuard + implements CanDeactivate +{ + canDeactivate(component: CanComponentDeactivate): CanDeactivateType { + return component.canDeactivate ? component.canDeactivate() : true; + } +} diff --git a/modules/ui/src/app/mocks/device.mock.ts b/modules/ui/src/app/mocks/device.mock.ts index 8bbfb56ea..86ef4ffd7 100644 --- a/modules/ui/src/app/mocks/device.mock.ts +++ b/modules/ui/src/app/mocks/device.mock.ts @@ -13,9 +13,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Device } from '../model/device'; +import { + Device, + DeviceStatus, + DeviceQuestionnaireSection, +} from '../model/device'; +import { ProfileRisk } from '../model/profile'; +import { FormControlType } from '../model/question'; export const device = { + status: DeviceStatus.VALID, + manufacturer: 'Delta', + model: 'O3-DIN-CPU', + mac_addr: '00:1e:42:35:73:c4', + test_modules: { + dns: { + enabled: true, + }, + }, +} as Device; + +export const expired_device = { + status: DeviceStatus.INVALID, manufacturer: 'Delta', model: 'O3-DIN-CPU', mac_addr: '00:1e:42:35:73:c4', @@ -26,6 +45,7 @@ export const device = { }, } as Device; export const updated_device = { + status: DeviceStatus.VALID, manufacturer: 'Alpha', model: 'O3-XYZ-CPU', mac_addr: '00:1e:42:35:73:11', @@ -45,8 +65,61 @@ export const MOCK_TEST_MODULES = [ { displayName: 'Udmi', name: 'udmi', - enabled: false, + enabled: true, }, ]; export const MOCK_MODULES = ['Connection', 'Udmi']; + +export const DEVICES_FORM: DeviceQuestionnaireSection[] = [ + { + step: 1, + title: 'Step 1 title', + description: 'Step 1 description', + questions: [ + { + id: 1, + question: 'What type of device is this?', + type: FormControlType.SELECT, + options: [ + { + text: 'Building Automation Gateway', + risk: ProfileRisk.HIGH, + id: 1, + }, + { + text: 'IoT Gateway', + risk: ProfileRisk.LIMITED, + id: 2, + }, + ], + }, + { + id: 2, + question: 'Does your device process any sensitive information? ', + type: FormControlType.SELECT, + options: [ + { + id: 1, + text: 'Yes', + risk: ProfileRisk.LIMITED, + }, + { + id: 2, + text: 'No', + risk: ProfileRisk.HIGH, + }, + ], + }, + { + id: 3, + question: 'Please select the technology this device falls into', + type: FormControlType.SELECT, + options: [ + { text: 'Hardware - Access Control' }, + { text: 'Hardware - Air quality' }, + ], + }, + ], + }, +]; diff --git a/modules/ui/src/app/mocks/profile.mock.ts b/modules/ui/src/app/mocks/profile.mock.ts index d53703809..3082d39d2 100644 --- a/modules/ui/src/app/mocks/profile.mock.ts +++ b/modules/ui/src/app/mocks/profile.mock.ts @@ -14,12 +14,8 @@ * limitations under the License. */ -import { - FormControlType, - Profile, - ProfileFormat, - ProfileStatus, -} from '../model/profile'; +import { Profile, ProfileFormat, ProfileStatus } from '../model/profile'; +import { FormControlType } from '../model/question'; export const PROFILE_MOCK: Profile = { name: 'Primary profile', diff --git a/modules/ui/src/app/mocks/reports.mock.ts b/modules/ui/src/app/mocks/reports.mock.ts index e1422a36c..2889b0571 100644 --- a/modules/ui/src/app/mocks/reports.mock.ts +++ b/modules/ui/src/app/mocks/reports.mock.ts @@ -1,16 +1,20 @@ import { HistoryTestrun, TestrunStatus } from '../model/testrun-status'; import { MatTableDataSource } from '@angular/material/table'; +import { DeviceStatus, TestingType } from '../model/device'; export const HISTORY = [ { mac_addr: '01:02:03:04:05:06', status: 'compliant', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:06', firmware: '1.2.2', + test_pack: TestingType.Qualification, }, + tags: [], report: 'https://api.testrun.io/report.pdf', started: '2023-06-23T10:11:00.123Z', finished: '2023-06-23T10:17:10.123Z', @@ -19,11 +23,14 @@ export const HISTORY = [ status: 'compliant', mac_addr: '01:02:03:04:05:07', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:07', firmware: '1.2.3', + test_pack: TestingType.Qualification, }, + tags: [], report: 'https://api.testrun.io/report.pdf', started: '2023-07-23T10:11:00.123Z', finished: '2023-07-23T10:17:10.123Z', @@ -32,11 +39,14 @@ export const HISTORY = [ mac_addr: null, status: 'compliant', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:08', firmware: '1.2.2', + test_pack: TestingType.Qualification, }, + tags: [], report: 'https://api.testrun.io/report.pdf', started: '2023-06-23T10:11:00.123Z', finished: '2023-06-23T10:17:10.123Z', @@ -48,11 +58,14 @@ export const HISTORY_AFTER_REMOVE = [ mac_addr: '01:02:03:04:05:06', status: 'compliant', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:06', firmware: '1.2.2', + test_pack: TestingType.Qualification, }, + tags: [], report: 'https://api.testrun.io/report.pdf', started: '2023-06-23T10:11:00.123Z', finished: '2023-06-23T10:17:10.123Z', @@ -61,11 +74,14 @@ export const HISTORY_AFTER_REMOVE = [ mac_addr: null, status: 'compliant', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:08', firmware: '1.2.2', + test_pack: TestingType.Qualification, }, + tags: [], report: 'https://api.testrun.io/report.pdf', started: '2023-06-23T10:11:00.123Z', finished: '2023-06-23T10:17:10.123Z', @@ -77,49 +93,61 @@ export const FORMATTED_HISTORY = [ status: 'compliant', mac_addr: '01:02:03:04:05:06', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:06', firmware: '1.2.2', + test_pack: TestingType.Qualification, }, + tags: [], report: 'https://api.testrun.io/report.pdf', started: '2023-06-23T10:11:00.123Z', finished: '2023-06-23T10:17:10.123Z', deviceFirmware: '1.2.2', deviceInfo: 'Delta 03-DIN-SRC', duration: '06m 10s', + program: 'Device Qualification', }, { status: 'compliant', mac_addr: '01:02:03:04:05:07', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:07', firmware: '1.2.3', + test_pack: TestingType.Qualification, }, + tags: [], report: 'https://api.testrun.io/report.pdf', started: '2023-07-23T10:11:00.123Z', finished: '2023-07-23T10:17:10.123Z', deviceFirmware: '1.2.3', deviceInfo: 'Delta 03-DIN-SRC', duration: '06m 10s', + program: 'Device Qualification', }, { mac_addr: null, status: 'compliant', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:08', firmware: '1.2.2', + test_pack: TestingType.Qualification, }, + tags: [], report: 'https://api.testrun.io/report.pdf', started: '2023-06-23T10:11:00.123Z', finished: '2023-06-23T10:17:10.123Z', deviceFirmware: '1.2.2', deviceInfo: 'Delta 03-DIN-SRC', duration: '06m 10s', + program: 'Device Qualification', }, ]; diff --git a/modules/ui/src/app/mocks/testrun.mock.ts b/modules/ui/src/app/mocks/testrun.mock.ts index bb588634c..c90927cd3 100644 --- a/modules/ui/src/app/mocks/testrun.mock.ts +++ b/modules/ui/src/app/mocks/testrun.mock.ts @@ -19,6 +19,7 @@ import { TestrunStatus, TestsData, } from '../model/testrun-status'; +import { DeviceStatus } from '../model/device'; export const TEST_DATA_RESULT: IResult[] = [ { @@ -32,6 +33,11 @@ export const TEST_DATA_RESULT: IResult[] = [ 'The device should use the DNS server provided by the DHCP server', result: 'Non-Compliant', }, + { + name: 'dns.mdns', + description: 'Does the device has MDNS (or any kind of IP multicast)', + result: 'Not Started', + }, ]; export const TEST_DATA_RESULT_WITH_RECOMMENDATIONS: IResult[] = [ @@ -47,9 +53,28 @@ export const TEST_DATA_RESULT_WITH_RECOMMENDATIONS: IResult[] = [ }, ]; +export const TEST_DATA_RESULT_WITH_ERROR: IResult[] = [ + { + name: 'dns.network.hostname_resolution', + description: 'The device should resolve hostnames', + result: 'Compliant', + }, + { + name: 'dns.network.from_dhcp', + description: + 'The device should use the DNS server provided by the DHCP server', + result: 'Error', + }, + { + name: 'dns.mdns', + description: 'Does the device has MDNS (or any kind of IP multicast)', + result: 'Not Started', + }, +]; + export const TEST_DATA_TABLE_RESULT: IResult[] = [ ...TEST_DATA_RESULT, - ...new Array(24).fill(null).map(() => ({}) as IResult), + ...new Array(23).fill(null).map(() => ({}) as IResult), ]; export const EMPTY_RESULT = new Array(100) @@ -71,6 +96,7 @@ const PROGRESS_DATA_RESPONSE = ( status, mac_addr: '01:02:03:04:05:06', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-CPU', mac_addr: '01:02:03:04:05:06', @@ -80,6 +106,7 @@ const PROGRESS_DATA_RESPONSE = ( finished, tests, report, + tags: ['VSA', 'Other tag', 'And one more'], }; }; @@ -131,3 +158,10 @@ export const MOCK_PROGRESS_DATA_WAITING_FOR_DEVICE: TestrunStatus = { status: StatusOfTestrun.WaitingForDevice, started: null, }; + +export const MOCK_PROGRESS_DATA_WITH_ERROR: TestrunStatus = + PROGRESS_DATA_RESPONSE(StatusOfTestrun.InProgress, null, { + ...TEST_DATA, + total: 3, + results: TEST_DATA_RESULT_WITH_ERROR, + }); diff --git a/modules/ui/src/app/model/callout-type.ts b/modules/ui/src/app/model/callout-type.ts index c784b46f6..c8f86d1c4 100644 --- a/modules/ui/src/app/model/callout-type.ts +++ b/modules/ui/src/app/model/callout-type.ts @@ -15,6 +15,7 @@ */ export enum CalloutType { Info = 'info', + InfoPilot = 'info pilot', Check = 'check_circle', Warning = 'warning_amber', Error = 'error', diff --git a/modules/ui/src/app/model/device.ts b/modules/ui/src/app/model/device.ts index ba526e661..55d383401 100644 --- a/modules/ui/src/app/model/device.ts +++ b/modules/ui/src/app/model/device.ts @@ -13,12 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { QuestionFormat } from './question'; +import { Question } from './profile'; + export interface Device { manufacturer: string; model: string; mac_addr: string; test_modules?: TestModules; firmware?: string; + status?: DeviceStatus; + type?: string; + technology?: string; + test_pack?: TestingType; + additional_info?: Question[]; +} + +export enum DeviceStatus { + VALID = 'Valid', + INVALID = 'Invalid', } /** @@ -43,3 +56,19 @@ export enum DeviceView { Basic = 'basic', WithActions = 'with actions', } + +export interface DeviceQuestionnaireSection { + step: number; + title?: string; + description?: string; + questions: QuestionnaireFormat[]; +} + +export interface QuestionnaireFormat extends QuestionFormat { + id: number; +} + +export enum TestingType { + Pilot = 'Pilot Assessment', + Qualification = 'Device Qualification', +} diff --git a/modules/ui/src/app/model/profile.ts b/modules/ui/src/app/model/profile.ts index 059b3cafe..37ee31b21 100644 --- a/modules/ui/src/app/model/profile.ts +++ b/modules/ui/src/app/model/profile.ts @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +import { QuestionFormat } from './question'; export interface Profile { name: string; risk?: string; @@ -22,27 +24,7 @@ export interface Profile { created?: string; } -export enum FormControlType { - SELECT = 'select', - TEXTAREA = 'text-long', - EMAIL_MULTIPLE = 'email-multiple', - SELECT_MULTIPLE = 'select-multiple', - TEXT = 'text', -} - -export interface Validation { - required?: boolean; - max?: string; -} - -export interface ProfileFormat { - question: string; - type: FormControlType; - description?: string; - options?: string[]; - default?: string; - validation?: Validation; -} +export type ProfileFormat = QuestionFormat; export interface Question { question?: string; diff --git a/modules/ui/src/app/model/program-type.ts b/modules/ui/src/app/model/program-type.ts new file mode 100644 index 000000000..6c6d8b9f5 --- /dev/null +++ b/modules/ui/src/app/model/program-type.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export enum ProgramType { + Pilot = 'pilot', + Qualification = 'qualification', +} diff --git a/modules/ui/src/app/model/question.ts b/modules/ui/src/app/model/question.ts new file mode 100644 index 000000000..284fcdd3d --- /dev/null +++ b/modules/ui/src/app/model/question.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export interface Validation { + required: boolean | undefined; + max?: string; +} + +export enum FormControlType { + SELECT = 'select', + TEXTAREA = 'text-long', + EMAIL_MULTIPLE = 'email-multiple', + SELECT_MULTIPLE = 'select-multiple', + TEXT = 'text', +} + +export interface QuestionFormat { + question: string; + type: FormControlType; + description?: string; + options?: OptionType[]; + default?: string; + validation?: Validation; +} + +export type OptionType = string | object; diff --git a/modules/ui/src/app/model/setting.ts b/modules/ui/src/app/model/setting.ts index 708dcfc94..02c3ec0ce 100644 --- a/modules/ui/src/app/model/setting.ts +++ b/modules/ui/src/app/model/setting.ts @@ -20,6 +20,7 @@ export interface SystemConfig { } | null; log_level?: string; monitor_period?: number; + single_intf?: boolean; } export interface InterfacesValidation { diff --git a/modules/ui/src/app/model/testrun-status.ts b/modules/ui/src/app/model/testrun-status.ts index 3bc63804c..faa707f92 100644 --- a/modules/ui/src/app/model/testrun-status.ts +++ b/modules/ui/src/app/model/testrun-status.ts @@ -18,16 +18,19 @@ import { Device } from './device'; export interface TestrunStatus { mac_addr: string | null; status: string; + description?: string; device: IDevice; started: string | null; finished: string | null; tests?: TestsResponse; report: string; + tags: string[] | null; } export interface HistoryTestrun extends TestrunStatus { deviceFirmware: string; deviceInfo: string; + program: string; duration: string; } @@ -84,6 +87,7 @@ export interface StatusResultClassName { green: boolean; red: boolean; blue: boolean; + cyan: boolean; grey: boolean; } @@ -98,6 +102,7 @@ export const IDLE_STATUS = { total: 0, results: [], }, + tags: [], } as TestrunStatus; export type TestrunStatusKey = keyof typeof StatusOfTestrun; diff --git a/modules/ui/src/app/model/version.ts b/modules/ui/src/app/model/version.ts index 21ec6ee38..5eb946e42 100644 --- a/modules/ui/src/app/model/version.ts +++ b/modules/ui/src/app/model/version.ts @@ -23,5 +23,4 @@ export interface Version { export interface ConsentDialogResult { grant: boolean; - isNavigateToRiskAssessment?: boolean; } diff --git a/modules/ui/src/app/pages/certificates/certificate.validator.ts b/modules/ui/src/app/pages/certificates/certificate.validator.ts index 9143824e8..72e197af1 100644 --- a/modules/ui/src/app/pages/certificates/certificate.validator.ts +++ b/modules/ui/src/app/pages/certificates/certificate.validator.ts @@ -9,7 +9,6 @@ export const getValidationErrors = (file: File): string[] => { errors.push(validateExtension(file.name)); errors.push(validateFileNameLength(file.name)); errors.push(validateSize(file.size)); - // @ts-expect-error null values are filtered return errors.filter(error => error !== null); }; const validateFileName = (name: string): string | null => { diff --git a/modules/ui/src/app/pages/certificates/certificates.component.html b/modules/ui/src/app/pages/certificates/certificates.component.html index d75f115b4..bcc5173b6 100644 --- a/modules/ui/src/app/pages/certificates/certificates.component.html +++ b/modules/ui/src/app/pages/certificates/certificates.component.html @@ -39,14 +39,5 @@

Certificates

deleteCertificate($event) ">
-
diff --git a/modules/ui/src/app/pages/certificates/certificates.component.spec.ts b/modules/ui/src/app/pages/certificates/certificates.component.spec.ts index c8f0e91b6..1deda3d21 100644 --- a/modules/ui/src/app/pages/certificates/certificates.component.spec.ts +++ b/modules/ui/src/app/pages/certificates/certificates.component.spec.ts @@ -89,20 +89,6 @@ describe('CertificatesComponent', () => { expect(component.closeCertificatedEvent.emit).toHaveBeenCalled(); }); - it('should emit closeSettingEvent when close button clicked', () => { - const headerCloseButton = fixture.nativeElement.querySelector( - '.close-button' - ) as HTMLButtonElement; - spyOn(component.closeCertificatedEvent, 'emit'); - - headerCloseButton.click(); - - expect(mockLiveAnnouncer.announce).toHaveBeenCalledWith( - 'The certificates panel is closed.' - ); - expect(component.closeCertificatedEvent.emit).toHaveBeenCalled(); - }); - it('should have upload file button', () => { const uploadCertificatesButton = fixture.nativeElement.querySelector( '.browse-files-button' @@ -166,9 +152,9 @@ describe('CertificatesComponent', () => { flush(); })); - it('should focus navigation button if next active element does not exist', fakeAsync(() => { + it('should focus navigation close button if next active element does not exist', fakeAsync(() => { const nextButton = window.document.querySelector( - '.certificates-drawer-content .close-button' + '.certificates-drawer-header .certificates-drawer-header-button' ) as HTMLButtonElement; const buttonFocusSpy = spyOn(nextButton, 'focus'); diff --git a/modules/ui/src/app/pages/certificates/certificates.component.ts b/modules/ui/src/app/pages/certificates/certificates.component.ts index bf6cf19fa..3b8cccccc 100644 --- a/modules/ui/src/app/pages/certificates/certificates.component.ts +++ b/modules/ui/src/app/pages/certificates/certificates.component.ts @@ -104,7 +104,7 @@ export class CertificatesComponent implements OnDestroy { } else { // If next interactive element doest not exist, close button will be focused const menuButton = window.document.querySelector( - '.certificates-drawer-content .close-button' + '.certificates-drawer-header .certificates-drawer-header-button' ) as HTMLButtonElement; menuButton?.focus(); } diff --git a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.html b/modules/ui/src/app/pages/devices/components/device-form/device-form.component.html deleted file mode 100644 index 5978f6d22..000000000 --- a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.html +++ /dev/null @@ -1,130 +0,0 @@ - -
- {{ data.title }} - - Device Manufacturer - - Please enter device manufacturer name - - Please, check. The manufacturer name must be a maximum of 28 - characters. Only letters, numbers, and accented letters are - permitted. - - - Device Manufacturer is required - - - - Device Model - - Please enter device name - - Please, check. The device model name must be a maximum of 28 - characters. Only letters, numbers, and accented letters are - permitted. - - - Device Model is required - - - - MAC address - - Please enter MAC address - - MAC address is required - - - Please, check. A MAC address consists of 12 hexadecimal digits (0 to 9, - a to f, or A to F). - - - This MAC address is already used for another device in the - repository. - - - - - - - {{ error$ | async }} - - - - - - - - - -
diff --git a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.scss b/modules/ui/src/app/pages/devices/components/device-form/device-form.component.scss deleted file mode 100644 index 831f9907a..000000000 --- a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.scss +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -@import 'src/theming/colors'; - -$device-form-max-width: 580px; -$device-form-min-width: 285px; - -:host { - display: grid; - grid-template-rows: 1fr; - overflow: auto; - grid-template-columns: minmax(285px, $device-form-max-width); -} - -.device-form { - display: grid; - padding: 24px; - max-width: $device-form-max-width; - min-width: $device-form-min-width; - gap: 10px; - overflow: auto; -} - -.manufacturer-field, -.model-field { - &::ng-deep.mat-mdc-form-field-subscript-wrapper:has(mat-error) { - height: 40px; - } -} - -.device-form-title { - color: $grey-800; - font-size: 22px; - line-height: 28px; - padding-bottom: 14px; -} - -.device-form-test-modules { - overflow: auto; - min-height: 78px; -} - -.device-form-actions { - padding: 0; - min-height: 30px; -} - -.close-button { - color: $primary; -} - -.device-form-mac-address-error { - white-space: nowrap; -} - -.delete-button { - color: $primary; - margin-right: auto; -} - -.hidden { - display: none; -} diff --git a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.spec.ts b/modules/ui/src/app/pages/devices/components/device-form/device-form.component.spec.ts deleted file mode 100644 index 6ea72aa52..000000000 --- a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.spec.ts +++ /dev/null @@ -1,459 +0,0 @@ -/** - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { - ComponentFixture, - fakeAsync, - flush, - TestBed, -} from '@angular/core/testing'; - -import { DeviceFormComponent, FormAction } from './device-form.component'; -import { MatButtonModule } from '@angular/material/button'; -import { FormControl, ReactiveFormsModule } from '@angular/forms'; -import { MatCheckboxModule } from '@angular/material/checkbox'; -import { MatInputModule } from '@angular/material/input'; -import { - MAT_DIALOG_DATA, - MatDialogModule, - MatDialogRef, -} from '@angular/material/dialog'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { Device } from '../../../../model/device'; -import { of } from 'rxjs'; -import { DeviceTestsComponent } from '../../../../components/device-tests/device-tests.component'; -import { SpinnerComponent } from '../../../../components/spinner/spinner.component'; -import { NgxMaskDirective, NgxMaskPipe, provideNgxMask } from 'ngx-mask'; -import { DevicesStore } from '../../devices.store'; -import { device, MOCK_TEST_MODULES } from '../../../../mocks/device.mock'; -import SpyObj = jasmine.SpyObj; - -describe('DeviceFormComponent', () => { - let component: DeviceFormComponent; - let fixture: ComponentFixture; - let mockDevicesStore: SpyObj; - let compiled: HTMLElement; - - beforeEach(() => { - mockDevicesStore = jasmine.createSpyObj('DevicesStore', [ - 'editDevice', - 'saveDevice', - ]); - - TestBed.configureTestingModule({ - declarations: [DeviceFormComponent], - providers: [ - { provide: DevicesStore, useValue: mockDevicesStore }, - { - provide: MatDialogRef, - useValue: { - keydownEvents: () => of(new KeyboardEvent('keydown', { code: '' })), - close: () => ({}), - }, - }, - { provide: MAT_DIALOG_DATA, useValue: {} }, - provideNgxMask(), - ], - imports: [ - MatButtonModule, - ReactiveFormsModule, - MatCheckboxModule, - MatInputModule, - MatDialogModule, - BrowserAnimationsModule, - DeviceTestsComponent, - SpinnerComponent, - NgxMaskDirective, - NgxMaskPipe, - ], - }); - TestBed.overrideProvider(DevicesStore, { useValue: mockDevicesStore }); - - fixture = TestBed.createComponent(DeviceFormComponent); - component = fixture.componentInstance; - compiled = fixture.nativeElement as HTMLElement; - component.data = { - testModules: MOCK_TEST_MODULES, - devices: [], - }; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should contain device form', () => { - const form = compiled.querySelector('.device-form'); - - expect(form).toBeTruthy(); - }); - - it('should close dialog on "cancel" click', () => { - const closeSpy = spyOn(component.dialogRef, 'close'); - const closeButton = compiled.querySelector( - '.close-button' - ) as HTMLButtonElement; - - closeButton?.click(); - - expect(closeSpy).toHaveBeenCalledWith(); - - closeSpy.calls.reset(); - }); - - it('should not save data when fields are empty', () => { - const saveButton = compiled.querySelector( - '.save-button' - ) as HTMLButtonElement; - const model: HTMLInputElement = compiled.querySelector( - '.device-form-model' - ) as HTMLInputElement; - const manufacturer: HTMLInputElement = compiled.querySelector( - '.device-form-manufacturer' - ) as HTMLInputElement; - const macAddress: HTMLInputElement = compiled.querySelector( - '.device-form-mac-address' - ) as HTMLInputElement; - - ['', ' '].forEach(value => { - model.value = value; - model.dispatchEvent(new Event('input')); - manufacturer.value = value; - manufacturer.dispatchEvent(new Event('input')); - macAddress.value = value; - macAddress.dispatchEvent(new Event('input')); - saveButton?.click(); - fixture.detectChanges(); - - const requiredErrors = compiled.querySelectorAll('mat-error'); - expect(requiredErrors?.length).toEqual(3); - - requiredErrors.forEach(error => { - expect(error?.innerHTML).toContain('required'); - }); - }); - }); - - it('should not save data if no test selected', fakeAsync(() => { - component.model.setValue('model'); - component.manufacturer.setValue('manufacturer'); - component.mac_addr.setValue('07:07:07:07:07:07'); - component.test_modules.setValue([false, false]); - - component.saveDevice(); - fixture.detectChanges(); - - const error = compiled.querySelector('mat-error'); - expect(error?.innerHTML).toContain( - 'At least one test has to be selected to save a Device.' - ); - - flush(); - })); - - it('should save data when form is valid', () => { - const device: Device = { - manufacturer: 'manufacturer', - model: 'model', - mac_addr: '07:07:07:07:07:07', - test_modules: { - connection: { - enabled: true, - }, - udmi: { - enabled: false, - }, - }, - }; - component.model.setValue('model'); - component.manufacturer.setValue('manufacturer'); - component.mac_addr.setValue('07:07:07:07:07:07'); - - component.saveDevice(); - - const args = mockDevicesStore.saveDevice.calls.argsFor(0); - // @ts-expect-error config is in object - expect(args[0].device).toEqual(device); - expect(mockDevicesStore.saveDevice).toHaveBeenCalled(); - }); - - describe('test modules', () => { - it('should be present', () => { - const test = compiled.querySelectorAll('mat-checkbox'); - - expect(test.length).toEqual(2); - }); - - it('should be enabled', () => { - const tests = compiled.querySelectorAll('.device-form-test-modules p'); - - expect(tests[0].classList.contains('disabled')).toEqual(false); - }); - }); - - describe('device model', () => { - it('should not contain errors when input is correct', () => { - const model: HTMLInputElement = compiled.querySelector( - '.device-form-model' - ) as HTMLInputElement; - ['model', 'Gebäude', 'jardín'].forEach(value => { - model.value = value; - model.dispatchEvent(new Event('input')); - - const errors = component.model.errors; - const uiValue = model.value; - const formValue = component.model.value; - - expect(uiValue).toEqual(formValue); - expect(errors).toBeNull(); - }); - }); - - it('should have "invalid_format" error when field does not satisfy validation rules', () => { - [ - 'very long value very long value very long value very long value very long value very long value very long value', - 'as&@3$', - ].forEach(value => { - const model: HTMLInputElement = compiled.querySelector( - '.device-form-model' - ) as HTMLInputElement; - model.value = value; - model.dispatchEvent(new Event('input')); - component.model.markAsTouched(); - fixture.detectChanges(); - - const modelError = compiled.querySelector('mat-error')?.innerHTML; - const error = component.model.hasError('invalid_format'); - - expect(error).toBeTruthy(); - expect(modelError).toContain( - 'The device model name must be a maximum of 28 characters. Only letters, numbers, and accented letters are permitted.' - ); - }); - }); - }); - - describe('device manufacturer', () => { - it('should not contain errors when input is correct', () => { - const manufacturer: HTMLInputElement = compiled.querySelector( - '.device-form-manufacturer' - ) as HTMLInputElement; - ['manufacturer', 'Gebäude', 'jardín'].forEach(value => { - manufacturer.value = value; - manufacturer.dispatchEvent(new Event('input')); - - const errors = component.manufacturer.errors; - const uiValue = manufacturer.value; - const formValue = component.manufacturer.value; - - expect(uiValue).toEqual(formValue); - expect(errors).toBeNull(); - }); - }); - - it('should have "invalid_format" error when field does not satisfy validation', () => { - [ - 'very long value very long value very long value very long value very long value very long value very long value', - 'as&@3$', - ].forEach(value => { - const manufacturer: HTMLInputElement = compiled.querySelector( - '.device-form-manufacturer' - ) as HTMLInputElement; - manufacturer.value = value; - manufacturer.dispatchEvent(new Event('input')); - component.manufacturer.markAsTouched(); - fixture.detectChanges(); - - const manufacturerError = - compiled.querySelector('mat-error')?.innerHTML; - const error = component.manufacturer.hasError('invalid_format'); - - expect(error).toBeTruthy(); - expect(manufacturerError).toContain( - 'The manufacturer name must be a maximum of 28 characters. Only letters, numbers, and accented letters are permitted.' - ); - }); - }); - }); - - describe('mac address', () => { - it('should not be disabled', () => { - expect(component.mac_addr.disabled).toBeFalse(); - }); - - it('should not contain errors when input is correct', () => { - const macAddress: HTMLInputElement = compiled.querySelector( - '.device-form-mac-address' - ) as HTMLInputElement; - ['07:07:07:07:07:07', ' 07:07:07:07:07:07 '].forEach(value => { - macAddress.value = value; - macAddress.dispatchEvent(new Event('input')); - - const errors = component.mac_addr.errors; - const formValue = component.mac_addr.value; - - expect(macAddress.value).toEqual(formValue); - expect(errors).toBeNull(); - }); - }); - - it('should have "pattern" error when field does not satisfy pattern', () => { - ['value', 'q01e423573c4'].forEach(value => { - const macAddress: HTMLInputElement = compiled.querySelector( - '.device-form-mac-address' - ) as HTMLInputElement; - macAddress.value = value; - macAddress.dispatchEvent(new Event('input')); - component.mac_addr.markAsTouched(); - fixture.detectChanges(); - - const macAddressError = compiled.querySelector('mat-error')?.innerHTML; - const error = component.mac_addr.hasError('pattern'); - - expect(error).toBeTruthy(); - expect(macAddressError).toContain( - 'Please, check. A MAC address consists of 12 hexadecimal digits (0 to 9, a to f, or A to F).' - ); - }); - }); - - it('should have "has_same_mac_address" error when MAC address is already used', () => { - component.data = { - testModules: MOCK_TEST_MODULES, - devices: [device], - }; - component.ngOnInit(); - fixture.detectChanges(); - - const macAddress: HTMLInputElement = compiled.querySelector( - '.device-form-mac-address' - ) as HTMLInputElement; - macAddress.value = '00:1e:42:35:73:c4'; - macAddress.dispatchEvent(new Event('input')); - component.mac_addr.markAsTouched(); - fixture.detectChanges(); - - const macAddressError = compiled.querySelector('mat-error')?.innerHTML; - const error = component.mac_addr.hasError('has_same_mac_address'); - - expect(error).toBeTruthy(); - expect(macAddressError).toContain( - 'This MAC address is already used for another device in the repository.' - ); - }); - }); - - it('should have hidden delete device button', () => { - const deleteButton = compiled.querySelector( - '.delete-button' - ) as HTMLButtonElement; - - expect(deleteButton.classList.contains('hidden')).toBeTrue(); - }); - - describe('when device is present', () => { - beforeEach(() => { - component.data = { - devices: [device], - testModules: MOCK_TEST_MODULES, - device: { - manufacturer: 'Delta', - model: 'O3-DIN-CPU', - mac_addr: '00:1e:42:35:73:c4', - test_modules: { - udmi: { - enabled: true, - }, - }, - }, - }; - component.ngOnInit(); - fixture.detectChanges(); - }); - - it('should fill form values with device values', () => { - const model: HTMLInputElement = compiled.querySelector( - '.device-form-model' - ) as HTMLInputElement; - const manufacturer: HTMLInputElement = compiled.querySelector( - '.device-form-manufacturer' - ) as HTMLInputElement; - - expect(model.value).toEqual('O3-DIN-CPU'); - expect(manufacturer.value).toEqual('Delta'); - }); - - it('should save data even mac address already exist', fakeAsync(() => { - // fill the test controls - component.test_modules.push(new FormControl(false)); - component.test_modules.push(new FormControl(true)); - component.saveDevice(); - fixture.detectChanges(); - - fixture.whenStable().then(() => { - const error = compiled.querySelector('mat-error'); - expect(error).toBeFalse(); - }); - - const args = mockDevicesStore.editDevice.calls.argsFor(0); - // @ts-expect-error config is in object - expect(args[0].device).toEqual({ - manufacturer: 'Delta', - model: 'O3-DIN-CPU', - mac_addr: '00:1e:42:35:73:c4', - test_modules: { - connection: { - enabled: false, - }, - udmi: { - enabled: true, - }, - }, - }); - expect(mockDevicesStore.editDevice).toHaveBeenCalled(); - - flush(); - })); - - it('should have delete device button', () => { - const deleteButton = compiled.querySelector( - '.delete-button' - ) as HTMLButtonElement; - - expect(deleteButton.classList.contains('hidden')).toBeFalse(); - expect(deleteButton).toBeTruthy(); - }); - - it('should close dialog with delete action on "delete" click', () => { - const closeSpy = spyOn(component.dialogRef, 'close'); - const closeButton = compiled.querySelector( - '.delete-button' - ) as HTMLButtonElement; - - closeButton?.click(); - - expect(closeSpy).toHaveBeenCalledWith({ action: FormAction.Delete }); - - closeSpy.calls.reset(); - }); - }); - - it('should has loader element', () => { - const spinner = compiled.querySelector('app-spinner'); - - expect(spinner).toBeTruthy(); - }); -}); diff --git a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.ts b/modules/ui/src/app/pages/devices/components/device-form/device-form.component.ts deleted file mode 100644 index 38ab05ffa..000000000 --- a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.ts +++ /dev/null @@ -1,211 +0,0 @@ -/** - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; -import { - AbstractControl, - FormArray, - FormBuilder, - FormGroup, - Validators, -} from '@angular/forms'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; - -import { Device, TestModule } from '../../../../model/device'; -import { DeviceValidators } from './device.validators'; -import { Subject } from 'rxjs'; -import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; -import { EscapableDialogComponent } from '../../../../components/escapable-dialog/escapable-dialog.component'; -import { DevicesStore } from '../../devices.store'; - -const MAC_ADDRESS_PATTERN = - '^[\\s]*[a-fA-F0-9]{2}(?:[:][a-fA-F0-9]{2}){5}[\\s]*$'; - -interface DialogData { - title?: string; - device?: Device; - devices: Device[]; - testModules: TestModule[]; -} - -export enum FormAction { - Delete = 'Delete', - Save = 'Save', -} - -export interface FormResponse { - device?: Device; - action: FormAction; -} - -@Component({ - selector: 'app-device-form', - templateUrl: './device-form.component.html', - styleUrls: ['./device-form.component.scss'], - providers: [DevicesStore], -}) -export class DeviceFormComponent - extends EscapableDialogComponent - implements OnInit, OnDestroy -{ - deviceForm!: FormGroup; - testModules: TestModule[] = []; - error$: BehaviorSubject = new BehaviorSubject( - null - ); - private destroy$: Subject = new Subject(); - - constructor( - public override dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: DialogData, - private fb: FormBuilder, - private deviceValidators: DeviceValidators, - private devicesStore: DevicesStore - ) { - super(dialogRef); - } - - get model() { - return this.deviceForm.get('model') as AbstractControl; - } - - get manufacturer() { - return this.deviceForm.get('manufacturer') as AbstractControl; - } - - get mac_addr() { - return this.deviceForm.get('mac_addr') as AbstractControl; - } - - get test_modules() { - return this.deviceForm.controls['test_modules'] as FormArray; - } - - ngOnInit() { - this.createDeviceForm(); - this.testModules = this.data.testModules; - if (this.data.device) { - this.model.setValue(this.data.device.model); - this.manufacturer.setValue(this.data.device.manufacturer); - this.mac_addr.setValue(this.data.device.mac_addr); - } - } - - ngOnDestroy() { - this.destroy$.next(true); - this.destroy$.unsubscribe(); - } - - delete(): void { - this.dialogRef.close({ action: FormAction.Delete } as FormResponse); - } - - cancel(): void { - this.dialogRef.close(); - } - - saveDevice() { - this.checkMandatoryFields(); - if (this.deviceForm.invalid) { - this.deviceForm.markAllAsTouched(); - return; - } - - if (this.isAllTestsDisabled()) { - this.error$.next( - 'At least one test has to be selected to save a Device.' - ); - return; - } - - const device = this.createDeviceFromForm(); - - this.updateDevice(device, () => { - this.dialogRef.close({ - action: FormAction.Save, - device, - } as FormResponse); - }); - } - - private updateDevice(device: Device, callback: () => void) { - if (this.data.device) { - this.devicesStore.editDevice({ - device, - mac_addr: this.data.device.mac_addr, - onSuccess: callback, - }); - } else { - this.devicesStore.saveDevice({ device, onSuccess: callback }); - } - } - - private isAllTestsDisabled(): boolean { - return this.deviceForm.value.test_modules.every((enabled: boolean) => { - return !enabled; - }); - } - - private createDeviceFromForm(): Device { - const testModules: { [key: string]: { enabled: boolean } } = {}; - this.deviceForm.value.test_modules.forEach( - (enabled: boolean, i: number) => { - testModules[this.testModules[i]?.name] = { - enabled: enabled, - }; - } - ); - return { - model: this.model.value.trim(), - manufacturer: this.manufacturer.value.trim(), - mac_addr: this.mac_addr.value.trim(), - test_modules: testModules, - } as Device; - } - - /** - * Model, manufacturer, MAC address are mandatory. - * It should be checked on submit. Other validation happens on blur. - */ - private checkMandatoryFields() { - this.setRequiredErrorIfEmpty(this.model); - this.setRequiredErrorIfEmpty(this.manufacturer); - this.setRequiredErrorIfEmpty(this.mac_addr); - } - - private setRequiredErrorIfEmpty(control: AbstractControl) { - if (!control.value.trim()) { - control.setErrors({ required: true }); - } - } - - private createDeviceForm() { - this.deviceForm = this.fb.group({ - model: ['', [this.deviceValidators.deviceStringFormat()]], - manufacturer: ['', [this.deviceValidators.deviceStringFormat()]], - mac_addr: [ - '', - [ - Validators.pattern(MAC_ADDRESS_PATTERN), - this.deviceValidators.differentMACAddress( - this.data.devices, - this.data.device - ), - ], - ], - test_modules: new FormArray([]), - }); - } -} diff --git a/modules/ui/src/app/pages/devices/components/device-form/device.validators.ts b/modules/ui/src/app/pages/devices/components/device-form/device.validators.ts index 60ceb5d48..3ffa45802 100644 --- a/modules/ui/src/app/pages/devices/components/device-form/device.validators.ts +++ b/modules/ui/src/app/pages/devices/components/device-form/device.validators.ts @@ -54,6 +54,16 @@ export class DeviceValidators { }; } + public testModulesRequired(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + const value = control.value; + if (value.every((module: boolean) => !module)) { + return { required: true }; + } + return null; + }; + } + public differentMACAddress(devices: Device[], device?: Device): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { const value = control.value?.trim(); diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.html b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.html new file mode 100644 index 000000000..4bf0e13a5 --- /dev/null +++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.html @@ -0,0 +1,347 @@ + +
+ +
+ +

{{ data.title }}

+
+
+ + + +

+ {{ data.title }} dialogue step 1 +

+
+ + Device Manufacturer + + Please enter device manufacturer name + + Please, check. The manufacturer name must be a maximum of 28 + characters. Only letters, numbers, and accented letters are + permitted. + + + Device Manufacturer is required + + + + Device Model + + Please enter device name + + Please, check. The device model name must be a maximum of 28 + characters. Only letters, numbers, and accented letters are + permitted. + + + Device Model is required + + + + MAC address + + Please enter MAC address + + MAC address is required + + + Please, check. A MAC address consists of 12 hexadecimal digits (0 + to 9, a to f, or A to F). + + + This MAC address is already used for another device in the + repository. + + + + Please, select the testing journey for device + + + + + + Device Qualification + + + + + Pilot Assessment + + + + + + + At least one test has to be selected to save a Device. + +
+
+ + + +

+ {{ data.title }} dialogue step {{ step.step + 1 }} +

+
+

+ {{ step.title }} +

+

+ {{ step.description }} +

+ +
+
+
+ + +

+ {{ data.title }} dialogue last step +

+

+ {{ data.title }} dialogue step 4 +

+
+ + +
diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss new file mode 100644 index 000000000..3ce6ee85b --- /dev/null +++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss @@ -0,0 +1,359 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@use '@angular/material' as mat; +@import 'src/theming/colors'; +@import 'src/theming/variables'; + +$form-min-width: 732px; + +:host { + container-type: size; + container-name: qualification-form; + + display: grid; + grid-template-rows: 1fr; + overflow: auto; + grid-template-columns: minmax(285px, $form-max-width); + height: 100vh; + max-height: 978px; +} + +.device-qualification-form { + overflow: hidden; +} + +::ng-deep .device-form-test-modules { + overflow: auto; + min-height: 78px; + display: grid; + grid-template-columns: repeat(2, 1fr); + grid-template-rows: repeat(4, 1fr); + grid-auto-flow: column; + padding-top: 16px; + p { + margin: 8px 0; + } +} + +.close-button { + color: $primary; +} + +.device-form-mac-address-error { + white-space: nowrap; +} + +.hidden { + display: none; +} + +.device-qualification-form-header { + position: relative; + padding-top: 24px; + &-title { + margin: 0; + font-size: 32px; + font-style: normal; + font-weight: 400; + line-height: 40px; + color: $grey-800; + text-align: center; + padding: 38px 0; + background-image: url(/assets/icons/create_device_header.svg); + } + + &-close-button { + position: absolute; + right: 0; + top: 0; + min-width: 24px; + width: 24px; + height: 24px; + box-sizing: content-box; + line-height: normal !important; + padding: 0; + margin: 0; + + .close-button-icon { + width: 24px; + height: 24px; + margin: 0; + } + + ::ng-deep * { + line-height: inherit !important; + } + } +} + +.device-qualification-form-journey-label { + font-family: $font-secondary; + font-style: normal; + font-weight: 400; + font-size: 16px; + line-height: 24px; + letter-spacing: 0.1px; + color: $grey-800; + margin: 24px 16px 0 16px; +} + +.device-qualification-form-journey-button { + padding: 0 18px 0 24px; +} + +.device-qualification-form-journey-button-info { + display: flex; +} + +.device-qualification-form-journey-button-label { + font-family: $font-secondary; + font-style: normal; + font-weight: 500; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.2px; + color: $grey-800; +} + +.device-qualification-form-test-modules-container { + padding: 0 24px; +} + +.device-qualification-form-step-title { + margin: 0; + font-style: normal; + font-weight: 500; + font-size: 22px; + line-height: 28px; + text-align: center; + color: $grey-900; + padding: 0 24px; + display: inline-block; + height: 28px; +} + +.device-qualification-form-step-description { + font-family: $font-secondary; + text-align: center; + color: $grey-800; + margin: 0; + padding: 8px 16px 0 16px; +} + +.step-link { + color: $primary; + text-decoration: underline; + cursor: pointer; +} + +.device-qualification-form-step-content { + padding: 0 16px; + overflow: scroll; +} + +.device-qualification-form-page { + padding-top: 10px; + margin-top: -10px; + display: grid; + gap: 8px; + height: 100%; + overflow: hidden; + align-content: start; + &:has(.device-qualification-form-summary-container) { + grid-template-rows: min-content min-content 1fr min-content; + } +} + +.device-qualification-form-summary-container { + display: grid; + align-items: center; + justify-content: center; + overflow: scroll; + ::ng-deep { + .device-item, + .device-qualification-form-summary-info { + width: $device-item-width; + } + } +} + +.device-qualification-form-summary { + border-radius: 12px; + background: mat.m2-get-color-from-palette($color-primary, 50); + padding: 24px; + width: max-content; + margin-left: auto; + margin-right: auto; + margin-top: 24px; + &-error { + background: $red-50; + } +} + +.device-qualification-form-actions { + width: $device-item-width; + text-align: center; + padding: 8px 24px; + justify-self: center; + align-self: end; + &:has(.delete-button) { + text-align: right; + } + .close-button, + .delete-button { + border: 1px solid $lighter-grey; + } + .delete-button { + color: $primary; + float: left; + } + .close-button { + padding: 0 16px; + } + .save-button { + margin-left: 16px; + } +} + +.device-qualification-form-summary-info { + margin-top: 16px; + border-radius: 12px; + padding: 16px 24px; + background: #fff; + box-sizing: border-box; + &-title, + &-title-error { + font-size: 18px; + font-weight: 400; + line-height: 24px; + text-align: center; + color: $grey-900; + } + &-description { + font-family: $font-secondary; + font-size: 16px; + font-weight: 400; + line-height: 24px; + text-align: center; + color: $grey-800; + } + .info-label { + display: block; + font-family: $font-secondary; + font-size: 14px; + font-weight: 400; + line-height: 20px; + text-align: left; + color: $secondary; + } + .info-value { + display: block; + color: $grey-800; + font-family: $font-secondary; + font-size: 16px; + font-weight: 400; + line-height: 24px; + text-align: left; + } +} + +.device-qualification-form-summary-info-title-error { + display: flex; + align-items: center; + height: 48px; + margin: auto; + justify-content: center; + color: $red-800; + gap: 14px; + ::ng-deep mat-icon { + color: $red-800; + } +} + +.device-qualification-form-instructions { + margin-top: auto; + padding-top: 8px; + color: $grey-800; + text-align: center; + font-family: $font-secondary; + font-size: 16px; + width: 510px; + ul { + margin-bottom: 0; + text-align: left; + padding-left: 26px; + } + li { + line-height: 24px; + } +} + +::ng-deep mat-error { + background: $white; +} + +:host mat-form-field { + &::ng-deep.mat-mdc-form-field-subscript-wrapper:has( + mat-error.error-multiline + ) { + height: 46px; + } +} + +.device-qualification-form-test-modules-container-error + ::ng-deep + .device-tests-title { + color: $red-800; +} + +.device-qualification-form-test-modules-error { + padding: 0 24px; +} + +.form-content-summary { + display: grid; + grid-template-rows: 1fr auto; + grid-row-gap: 16px; + overflow: hidden; +} + +@container qualification-form (height < 870px) { + .device-qualification-form-page { + overflow: scroll; + ::ng-deep app-device-tests { + overflow: visible; + } + } +} + +@container qualification-form (height < 580px) { + .device-qualification-form-page { + overflow: scroll; + .device-qualification-form-step-content, + .device-qualification-form-summary-container { + overflow: visible; + } + } +} + +@container qualification-form (width < 360px) { + .manufacturer-field ::ng-deep mat-hint { + white-space: nowrap; + } + ::ng-deep .device-form-test-modules { + display: block; + } +} diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts new file mode 100644 index 000000000..e1b59025b --- /dev/null +++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts @@ -0,0 +1,760 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + ComponentFixture, + discardPeriodicTasks, + fakeAsync, + TestBed, + tick, +} from '@angular/core/testing'; + +import { DeviceQualificationFromComponent } from './device-qualification-from.component'; +import { + MAT_DIALOG_DATA, + MatDialogModule, + MatDialogRef, +} from '@angular/material/dialog'; +import { of } from 'rxjs'; +import { NgxMaskDirective, NgxMaskPipe, provideNgxMask } from 'ngx-mask'; +import { MatButtonModule } from '@angular/material/button'; +import { FormArray, ReactiveFormsModule } from '@angular/forms'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatInputModule } from '@angular/material/input'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { DeviceTestsComponent } from '../../../../components/device-tests/device-tests.component'; +import { SpinnerComponent } from '../../../../components/spinner/spinner.component'; +import { + device, + DEVICES_FORM, + MOCK_TEST_MODULES, +} from '../../../../mocks/device.mock'; +import { MatIconTestingModule } from '@angular/material/icon/testing'; +import { TestRunService } from '../../../../services/test-run.service'; +import { DevicesStore } from '../../devices.store'; +import { provideMockStore } from '@ngrx/store/testing'; +import { FormAction } from '../../devices.component'; +import { DeviceStatus, TestingType } from '../../../../model/device'; +import { Component, Input } from '@angular/core'; +import { QuestionFormat } from '../../../../model/question'; +import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; + +describe('DeviceQualificationFromComponent', () => { + let component: DeviceQualificationFromComponent; + let fixture: ComponentFixture; + let compiled: HTMLElement; + const testrunServiceMock: jasmine.SpyObj = + jasmine.createSpyObj('testrunServiceMock', ['fetchQuestionnaireFormat']); + const keyboardEvent = new BehaviorSubject( + new KeyboardEvent('keydown', { code: '' }) + ); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [FakeDynamicFormComponent], + imports: [ + DeviceQualificationFromComponent, + MatButtonModule, + ReactiveFormsModule, + MatCheckboxModule, + MatInputModule, + MatDialogModule, + BrowserAnimationsModule, + DeviceTestsComponent, + SpinnerComponent, + NgxMaskDirective, + NgxMaskPipe, + MatIconTestingModule, + ], + providers: [ + DevicesStore, + { + provide: MatDialogRef, + useValue: { + keydownEvents: () => keyboardEvent.asObservable(), + close: () => ({}), + }, + }, + { provide: MAT_DIALOG_DATA, useValue: {} }, + { provide: TestRunService, useValue: testrunServiceMock }, + provideNgxMask(), + provideMockStore({}), + ], + }).compileComponents(); + + fixture = TestBed.createComponent(DeviceQualificationFromComponent); + component = fixture.componentInstance; + compiled = fixture.nativeElement as HTMLElement; + + component.data = { + testModules: MOCK_TEST_MODULES, + devices: [], + index: 0, + isCreate: true, + }; + testrunServiceMock.fetchQuestionnaireFormat.and.returnValue( + of(DEVICES_FORM) + ); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should contain device form', () => { + fixture.detectChanges(); + const form = compiled.querySelector('.device-qualification-form'); + + expect(form).toBeTruthy(); + }); + + it('should fetch devices format', () => { + fixture.detectChanges(); + const getQuestionnaireFormatSpy = spyOn( + component.devicesStore, + 'getQuestionnaireFormat' + ); + component.ngOnInit(); + fixture.detectChanges(); + + expect(getQuestionnaireFormatSpy).toHaveBeenCalled(); + }); + + it('should close dialog on "cancel" click with do data if form has no changes', () => { + fixture.detectChanges(); + const closeSpy = spyOn(component.dialogRef, 'close'); + const closeButton = compiled.querySelector( + '.device-qualification-form-header-close-button' + ) as HTMLButtonElement; + + closeButton?.click(); + + expect(closeSpy).toHaveBeenCalledWith(); + + closeSpy.calls.reset(); + }); + + it('should close dialog on escape', fakeAsync(() => { + const closeSpy = spyOn(component.dialogRef, 'close'); + fixture.detectChanges(); + + keyboardEvent.next(new KeyboardEvent('keydown', { code: 'Escape' })); + + tick(); + + expect(closeSpy).toHaveBeenCalledWith(); + + closeSpy.calls.reset(); + })); + + it('should close dialog on "cancel" click', () => { + fixture.detectChanges(); + component.manufacturer.setValue('test'); + ( + component.deviceQualificationForm.get('steps') as FormArray + ).controls.forEach(control => control.markAsDirty()); + fixture.detectChanges(); + const closeSpy = spyOn(component.dialogRef, 'close'); + const closeButton = compiled.querySelector( + '.device-qualification-form-header-close-button' + ) as HTMLButtonElement; + + closeButton?.click(); + + expect(closeSpy).toHaveBeenCalledWith({ + action: FormAction.Close, + index: 0, + device: { + status: DeviceStatus.VALID, + manufacturer: 'test', + model: '', + mac_addr: '', + test_pack: 'Device Qualification', + type: '', + technology: '', + test_modules: { + udmi: { + enabled: true, + }, + connection: { + enabled: true, + }, + }, + additional_info: [ + { question: 'What type of device is this?', answer: '' }, + { + question: 'Does your device process any sensitive information? ', + answer: '', + }, + { + question: 'Please select the technology this device falls into', + answer: '', + }, + ], + }, + }); + + closeSpy.calls.reset(); + }); + + describe('test modules', () => { + beforeEach(() => { + fixture.detectChanges(); + }); + + it('should be present', () => { + const test = compiled.querySelectorAll('mat-checkbox'); + + expect(test.length).toEqual(2); + }); + + it('should be enabled', () => { + const tests = compiled.querySelectorAll('.device-form-test-modules p'); + + expect(tests[0].classList.contains('disabled')).toEqual(false); + }); + + it('should have error when no modules selected', () => { + component.test_modules.setValue([false, false]); + component.test_modules.markAsTouched(); + fixture.detectChanges(); + const modules = compiled.querySelector( + '.device-qualification-form-test-modules-container-error' + ); + const error = compiled.querySelector( + '.device-qualification-form-test-modules-error' + ); + + expect(modules).toBeTruthy(); + expect(error?.innerHTML.trim()).toEqual( + 'At least one test has to be selected to save a Device.' + ); + }); + }); + + describe('device model', () => { + beforeEach(() => { + fixture.detectChanges(); + }); + + it('should not contain errors when input is correct', () => { + const model: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-model' + ) as HTMLInputElement; + ['model', 'Gebäude', 'jardín'].forEach(value => { + model.value = value; + model.dispatchEvent(new Event('input')); + + const errors = component.model.errors; + const uiValue = model.value; + const formValue = component.model.value; + + expect(uiValue).toEqual(formValue); + expect(errors).toBeNull(); + }); + }); + + it('should have "invalid_format" error when field does not satisfy validation rules', () => { + [ + 'very long value very long value very long value very long value very long value very long value very long value', + 'as&@3$', + ].forEach(value => { + const model: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-model' + ) as HTMLInputElement; + model.value = value; + model.dispatchEvent(new Event('input')); + component.model.markAsTouched(); + fixture.detectChanges(); + + const modelError = compiled.querySelector('mat-error')?.innerHTML; + const error = component.model.hasError('invalid_format'); + + expect(error).toBeTruthy(); + expect(modelError).toContain( + 'The device model name must be a maximum of 28 characters. Only letters, numbers, and accented letters are permitted.' + ); + }); + }); + }); + + describe('device manufacturer', () => { + beforeEach(() => { + fixture.detectChanges(); + }); + + it('should not contain errors when input is correct', () => { + const manufacturer: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-manufacturer' + ) as HTMLInputElement; + ['manufacturer', 'Gebäude', 'jardín'].forEach(value => { + manufacturer.value = value; + manufacturer.dispatchEvent(new Event('input')); + + const errors = component.manufacturer.errors; + const uiValue = manufacturer.value; + const formValue = component.manufacturer.value; + + expect(uiValue).toEqual(formValue); + expect(errors).toBeNull(); + }); + }); + + it('should have "invalid_format" error when field does not satisfy validation', () => { + [ + 'very long value very long value very long value very long value very long value very long value very long value', + 'as&@3$', + ].forEach(value => { + const manufacturer: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-manufacturer' + ) as HTMLInputElement; + manufacturer.value = value; + manufacturer.dispatchEvent(new Event('input')); + component.manufacturer.markAsTouched(); + fixture.detectChanges(); + + const manufacturerError = + compiled.querySelector('mat-error')?.innerHTML; + const error = component.manufacturer.hasError('invalid_format'); + + expect(error).toBeTruthy(); + expect(manufacturerError).toContain( + 'The manufacturer name must be a maximum of 28 characters. Only letters, numbers, and accented letters are permitted.' + ); + }); + }); + }); + + describe('mac address', () => { + beforeEach(() => { + fixture.detectChanges(); + }); + + it('should not be disabled', () => { + expect(component.mac_addr.disabled).toBeFalse(); + }); + + it('should not contain errors when input is correct', () => { + const macAddress: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-mac-address' + ) as HTMLInputElement; + ['07:07:07:07:07:07', ' 07:07:07:07:07:07 '].forEach(value => { + macAddress.value = value; + macAddress.dispatchEvent(new Event('input')); + + const errors = component.mac_addr.errors; + const formValue = component.mac_addr.value; + + expect(macAddress.value).toEqual(formValue); + expect(errors).toBeNull(); + }); + }); + + it('should have "pattern" error when field does not satisfy pattern', () => { + ['value', 'q01e423573c4'].forEach(value => { + const macAddress: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-mac-address' + ) as HTMLInputElement; + macAddress.value = value; + macAddress.dispatchEvent(new Event('input')); + component.mac_addr.markAsTouched(); + fixture.detectChanges(); + + const macAddressError = compiled.querySelector('mat-error')?.innerHTML; + const error = component.mac_addr.hasError('pattern'); + + expect(error).toBeTruthy(); + expect(macAddressError).toContain( + 'Please, check. A MAC address consists of 12 hexadecimal digits (0 to 9, a to f, or A to F).' + ); + }); + }); + + it('should have "has_same_mac_address" error when MAC address is already used', () => { + component.data = { + testModules: MOCK_TEST_MODULES, + devices: [device], + index: 0, + isCreate: true, + }; + component.ngOnInit(); + fixture.detectChanges(); + + const macAddress: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-mac-address' + ) as HTMLInputElement; + macAddress.value = '00:1e:42:35:73:c4'; + macAddress.dispatchEvent(new Event('input')); + component.mac_addr.markAsTouched(); + fixture.detectChanges(); + + const macAddressError = compiled.querySelector('mat-error')?.innerHTML; + const error = component.mac_addr.hasError('has_same_mac_address'); + + expect(error).toBeTruthy(); + expect(macAddressError).toContain( + 'This MAC address is already used for another device in the repository.' + ); + }); + }); + + describe('when device is present', () => { + beforeEach(() => { + component.data = { + devices: [device], + testModules: MOCK_TEST_MODULES, + device: { + status: DeviceStatus.VALID, + manufacturer: 'Delta', + model: 'O3-DIN-CPU', + mac_addr: '00:1e:42:35:73:c4', + test_modules: { + udmi: { + enabled: true, + }, + }, + }, + isCreate: false, + index: 0, + }; + }); + + it('should fill form values with device values', fakeAsync(() => { + fixture.detectChanges(); + + testrunServiceMock.fetchQuestionnaireFormat.and.returnValue( + of(DEVICES_FORM) + ); + + tick(1); + + const model: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-model' + ) as HTMLInputElement; + const manufacturer: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-manufacturer' + ) as HTMLInputElement; + + expect(model.value).toEqual('O3-DIN-CPU'); + expect(manufacturer.value).toEqual('Delta'); + + discardPeriodicTasks(); + })); + }); + + describe('steps', () => { + beforeEach(() => { + fixture.detectChanges(); + }); + + describe('with questionnaire', () => { + it('should have steps', () => { + expect( + (component.deviceQualificationForm.get('steps') as FormArray).controls + .length + ).toEqual(3); + }); + }); + + it('should not save data when fields are empty', () => { + const forwardButton = compiled.querySelector( + '.form-button-forward' + ) as HTMLButtonElement; + const model: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-model' + ) as HTMLInputElement; + const manufacturer: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-manufacturer' + ) as HTMLInputElement; + const macAddress: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-mac-address' + ) as HTMLInputElement; + + ['', ' '].forEach(value => { + model.value = value; + model.dispatchEvent(new Event('input')); + manufacturer.value = value; + manufacturer.dispatchEvent(new Event('input')); + macAddress.value = value; + macAddress.dispatchEvent(new Event('input')); + forwardButton?.click(); + fixture.detectChanges(); + + const requiredErrors = compiled.querySelectorAll('mat-error'); + expect(requiredErrors?.length).toEqual(3); + + requiredErrors.forEach(error => { + expect(error?.innerHTML).toContain('required'); + }); + }); + }); + + describe('happy flow', () => { + beforeEach(() => { + component.model.setValue('model'); + component.manufacturer.setValue('manufacturer'); + component.mac_addr.setValue('07:07:07:07:07:07'); + component.test_modules.setValue([true, true]); + }); + + it('should save device when step is changed', () => { + const forwardButton = compiled.querySelector( + '.form-button-forward' + ) as HTMLButtonElement; + forwardButton.click(); + + expect(component.device).toEqual({ + status: DeviceStatus.VALID, + manufacturer: 'manufacturer', + model: 'model', + mac_addr: '07:07:07:07:07:07', + test_pack: TestingType.Qualification, + type: '', + technology: '', + test_modules: { + udmi: { + enabled: true, + }, + connection: { + enabled: true, + }, + }, + additional_info: [ + { question: 'What type of device is this?', answer: '' }, + { + question: 'Does your device process any sensitive information? ', + answer: '', + }, + { + question: 'Please select the technology this device falls into', + answer: '', + }, + ], + }); + }); + + describe('summary', () => { + beforeEach(() => { + const forwardButton = compiled.querySelector( + '.form-button-forward' + ) as HTMLButtonElement; + forwardButton.click(); // will redirect to 2 step + fixture.detectChanges(); + + const nextForwardButton = compiled.querySelector( + '.form-button-forward' + ) as HTMLButtonElement; + nextForwardButton.click(); //will redirect to summary + + fixture.detectChanges(); + }); + + it('should have device item', () => { + const item = compiled.querySelector('app-device-item'); + expect(item).toBeTruthy(); + }); + + it('should have instructions', () => { + const instructions = compiled.querySelector( + '.device-qualification-form-instructions' + ); + expect(instructions).toBeTruthy(); + }); + + it('should not have instructions when device is editing', () => { + component.data = { + devices: [device], + testModules: MOCK_TEST_MODULES, + device: { + status: DeviceStatus.VALID, + manufacturer: 'Delta', + model: 'O3-DIN-CPU', + mac_addr: '00:1e:42:35:73:c4', + test_modules: { + udmi: { + enabled: true, + }, + }, + }, + isCreate: false, + index: 0, + }; + fixture.detectChanges(); + + const instructions = compiled.querySelector( + '.device-qualification-form-instructions' + ); + expect(instructions).toBeNull(); + }); + + it('should save device', () => { + const saveSpy = spyOn(component.devicesStore, 'saveDevice'); + + component.submit(); + + const args = saveSpy.calls.argsFor(0); + // @ts-expect-error config is in object + expect(args[0].device).toEqual({ + status: DeviceStatus.VALID, + manufacturer: 'manufacturer', + model: 'model', + mac_addr: '07:07:07:07:07:07', + test_pack: 'Device Qualification', + type: '', + technology: '', + test_modules: { + connection: { + enabled: true, + }, + udmi: { + enabled: true, + }, + }, + additional_info: [ + { question: 'What type of device is this?', answer: '' }, + { + question: + 'Does your device process any sensitive information? ', + answer: '', + }, + { + question: 'Please select the technology this device falls into', + answer: '', + }, + ], + }); + expect(saveSpy).toHaveBeenCalled(); + }); + + it('should edit device', () => { + component.data = { + devices: [device], + testModules: MOCK_TEST_MODULES, + device: { + status: DeviceStatus.VALID, + manufacturer: 'Delta', + model: 'O3-DIN-CPU', + mac_addr: '00:1e:42:35:73:c4', + test_modules: { + udmi: { + enabled: true, + }, + }, + }, + isCreate: false, + index: 0, + }; + fixture.detectChanges(); + const editSpy = spyOn(component.devicesStore, 'editDevice'); + + component.submit(); + + const args = editSpy.calls.argsFor(0); + // @ts-expect-error config is in object + expect(args[0].device).toEqual({ + status: DeviceStatus.VALID, + manufacturer: 'manufacturer', + model: 'model', + mac_addr: '07:07:07:07:07:07', + test_pack: 'Device Qualification', + type: '', + technology: '', + test_modules: { + connection: { + enabled: true, + }, + udmi: { + enabled: true, + }, + }, + additional_info: [ + { question: 'What type of device is this?', answer: '' }, + { + question: + 'Does your device process any sensitive information? ', + answer: '', + }, + { + question: 'Please select the technology this device falls into', + answer: '', + }, + ], + }); + expect(editSpy).toHaveBeenCalled(); + }); + }); + }); + + describe('with errors', () => { + beforeEach(() => { + component.data = { + devices: [device], + testModules: MOCK_TEST_MODULES, + device: { + status: DeviceStatus.VALID, + manufacturer: 'Delta', + model: 'O3-DIN-CPU', + mac_addr: '00:1e:42:35:73:c4', + test_modules: { + udmi: { + enabled: true, + }, + }, + }, + isCreate: false, + index: 0, + }; + component.model.setValue(''); + + fixture.detectChanges(); + }); + + describe('summary', () => { + beforeEach(() => { + const forwardButton = compiled.querySelector( + '.form-button-forward' + ) as HTMLButtonElement; + forwardButton.click(); // will redirect to 2 step + fixture.detectChanges(); + + const nextForwardButton = compiled.querySelector( + '.form-button-forward' + ) as HTMLButtonElement; + nextForwardButton.click(); //will redirect to summary + fixture.detectChanges(); + }); + + it('should have error message', () => { + const error = compiled.querySelector( + '.device-qualification-form-summary-info-description' + ); + expect(error?.textContent?.trim()).toEqual( + 'Please go back and correct the errors on Step 1.' + ); + }); + }); + }); + }); +}); + +@Component({ + selector: 'app-dynamic-form', + template: '
', +}) +class FakeDynamicFormComponent { + @Input() format: QuestionFormat[] = []; + @Input() optionKey: string | undefined; +} diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts new file mode 100644 index 000000000..67eb39d4c --- /dev/null +++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts @@ -0,0 +1,574 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + AfterViewInit, + Component, + ElementRef, + HostListener, + Inject, + OnDestroy, + OnInit, + ViewChild, +} from '@angular/core'; +import { + AbstractControl, + FormArray, + FormBuilder, + FormGroup, + ReactiveFormsModule, + Validators, +} from '@angular/forms'; +import { DeviceValidators } from '../device-form/device.validators'; +import { + Device, + DeviceQuestionnaireSection, + DeviceStatus, + DeviceView, + TestingType, + TestModule, +} from '../../../../model/device'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { CommonModule } from '@angular/common'; +import { CdkStep, StepperSelectionEvent } from '@angular/cdk/stepper'; +import { StepperComponent } from '../../../../components/stepper/stepper.component'; +import { + MatError, + MatFormField, + MatFormFieldModule, +} from '@angular/material/form-field'; +import { DeviceTestsComponent } from '../../../../components/device-tests/device-tests.component'; +import { MatButtonModule } from '@angular/material/button'; +import { MatInputModule } from '@angular/material/input'; +import { MatSelectModule } from '@angular/material/select'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { TextFieldModule } from '@angular/cdk/text-field'; +import { NgxMaskDirective, NgxMaskPipe, provideNgxMask } from 'ngx-mask'; +import { MatIcon } from '@angular/material/icon'; +import { MatRadioButton, MatRadioGroup } from '@angular/material/radio'; +import { ProfileValidators } from '../../../risk-assessment/profile-form/profile.validators'; +import { DevicesStore } from '../../devices.store'; +import { DynamicFormComponent } from '../../../../components/dynamic-form/dynamic-form.component'; +import { filter, skip, Subject, takeUntil, timer } from 'rxjs'; +import { FormAction, FormResponse } from '../../devices.component'; +import { DeviceItemComponent } from '../../../../components/device-item/device-item.component'; +import { ProgramTypeIconComponent } from '../../../../components/program-type-icon/program-type-icon.component'; +import { Question } from '../../../../model/profile'; +import { FormControlType } from '../../../../model/question'; +import { ProgramType } from '../../../../model/program-type'; +import { FocusManagerService } from '../../../../services/focus-manager.service'; + +const MAC_ADDRESS_PATTERN = + '^[\\s]*[a-fA-F0-9]{2}(?:[:][a-fA-F0-9]{2}){5}[\\s]*$'; + +interface DialogData { + title?: string; + device?: Device; + initialDevice?: Device; + devices: Device[]; + testModules: TestModule[]; + index: number; + isCreate: boolean; +} + +@Component({ + selector: 'app-device-qualification-from', + standalone: true, + imports: [ + CdkStep, + StepperComponent, + MatFormField, + DeviceTestsComponent, + MatButtonModule, + CommonModule, + ReactiveFormsModule, + MatInputModule, + MatError, + MatFormFieldModule, + MatSelectModule, + MatCheckboxModule, + TextFieldModule, + NgxMaskDirective, + NgxMaskPipe, + MatIcon, + MatRadioGroup, + MatRadioButton, + DynamicFormComponent, + DeviceItemComponent, + ProgramTypeIconComponent, + ], + providers: [provideNgxMask(), DevicesStore], + templateUrl: './device-qualification-from.component.html', + styleUrl: './device-qualification-from.component.scss', +}) +export class DeviceQualificationFromComponent + implements OnInit, AfterViewInit, OnDestroy +{ + readonly FORM_HEIGHT = 993; + readonly TestingType = TestingType; + readonly DeviceView = DeviceView; + readonly ProgramType = ProgramType; + @ViewChild('stepper') public stepper!: StepperComponent; + testModules: TestModule[] = []; + deviceQualificationForm: FormGroup = this.fb.group({}); + device: Device | undefined; + format: DeviceQuestionnaireSection[] = []; + selectedIndex: number = 0; + typeStep = 1; + typeQuestion = 0; + technologyStep = 1; + technologyQuestion = 2; + + private destroy$: Subject = new Subject(); + + get model() { + return this.getStep(0).get('model') as AbstractControl; + } + + get manufacturer() { + return this.getStep(0).get('manufacturer') as AbstractControl; + } + + get mac_addr() { + return this.getStep(0).get('mac_addr') as AbstractControl; + } + + get test_pack() { + return this.getStep(0).get('test_pack') as AbstractControl; + } + + get type() { + return this.getStep(this.typeStep)?.get( + this.typeQuestion.toString() + ) as AbstractControl; + } + + get technology() { + return this.getStep(this.technologyStep)?.get( + this.technologyQuestion.toString() + ) as AbstractControl; + } + + get test_modules() { + return this.getStep(0).controls['test_modules'] as FormArray; + } + + get formValid() { + return ( + this.deviceQualificationForm.get('steps') as FormArray + ).controls.every(control => (control as FormGroup).valid); + } + + deviceHasNoChanges(device1: Device | undefined, device2: Device | undefined) { + return device1 && device2 && this.compareDevices(device1, device2); + } + + @HostListener('window:resize', ['$event']) + onResize() { + this.setDialogHeight(); + } + + constructor( + private fb: FormBuilder, + private deviceValidators: DeviceValidators, + private profileValidators: ProfileValidators, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: DialogData, + public devicesStore: DevicesStore, + private element: ElementRef, + private focusService: FocusManagerService + ) { + this.device = data.device; + } + + ngOnInit(): void { + this.createBasicStep(); + this.testModules = this.data.testModules; + + this.devicesStore.questionnaireFormat$.pipe(skip(1)).subscribe(format => { + this.createDeviceForm(format); + this.format = format; + + format.forEach(step => { + step.questions.forEach((question, index) => { + // need to define the step and index of type and technology + if (question.question.toLowerCase().includes('type')) { + this.typeStep = step.step; + this.typeQuestion = index; + } else if (question.question.toLowerCase().includes('technology')) { + this.technologyStep = step.step; + this.technologyQuestion = index; + } + }); + }); + + timer(0) + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + if (this.data.device) { + this.fillDeviceForm(this.format, this.data.device!); + } + if (this.data.index) { + // previous steps should be marked as interacted + for (let i = 0; i <= this.data.index; i++) { + this.goToStep(i); + } + } + this.dialogRef + .keydownEvents() + .pipe(filter((e: KeyboardEvent) => e.code === 'Escape')) + .subscribe(() => { + this.closeForm(); + }); + }); + }); + + this.devicesStore.getQuestionnaireFormat(); + } + + ngAfterViewInit() { + //set static height for better UX + this.element.nativeElement.style.height = + this.element.nativeElement.offsetHeight + 'px'; + } + + ngOnDestroy() { + this.destroy$.next(true); + this.destroy$.unsubscribe(); + } + + submit(): void { + this.updateDevice(this.device!, () => { + this.dialogRef.close({ + action: FormAction.Save, + device: this.device, + } as FormResponse); + }); + } + + delete(): void { + this.dialogRef.close({ + action: FormAction.Delete, + device: this.createDeviceFromForm(), + index: this.stepper.selectedIndex, + } as FormResponse); + } + + closeForm(): void { + const device1 = this.data.initialDevice; + const device2 = this.createDeviceFromForm(); + if ( + (device1 && device2 && this.compareDevices(device1, device2)) || + (!device1 && this.deviceIsEmpty(device2)) + ) { + this.dialogRef.close(); + } else { + this.dialogRef.close({ + action: FormAction.Close, + device: this.createDeviceFromForm(), + index: this.stepper.selectedIndex, + } as FormResponse); + } + } + + getStep(step: number) { + return (this.deviceQualificationForm.get('steps') as FormArray).controls[ + step + ] as FormGroup; + } + + onStepChange(event: StepperSelectionEvent) { + this.focusService.focusFirstElementInContainer(); + if (event.previouslySelectedStep.completed) { + this.device = this.createDeviceFromForm(); + } + } + + getErrorSteps(): number[] { + const steps: number[] = []; + (this.deviceQualificationForm.get('steps') as FormArray).controls.forEach( + (control, index) => { + if (!control.valid) steps.push(index); + } + ); + return steps; + } + + goToStep(index: number, event?: Event) { + event?.preventDefault(); + this.stepper.selectedIndex = index; + } + + private fillDeviceForm( + format: DeviceQuestionnaireSection[], + device: Device + ): void { + format.forEach(step => { + step.questions.forEach((question, index) => { + const answer = device.additional_info?.find( + answers => answers.question === question.question + )?.answer; + if (answer !== undefined && answer !== null && answer !== '') { + if (question.type === FormControlType.SELECT_MULTIPLE) { + question.options?.forEach((item, idx) => { + if ((answer as number[])?.includes(idx)) { + ( + this.getStep(step.step).get(index.toString()) as FormGroup + ).controls[idx].setValue(true); + } else { + ( + this.getStep(step.step).get(index.toString()) as FormGroup + ).controls[idx].setValue(false); + } + }); + } else { + ( + this.getStep(step.step).get(index.toString()) as AbstractControl + ).setValue(answer || ''); + } + } else { + ( + this.getStep(step.step)?.get(index.toString()) as AbstractControl + )?.markAsTouched(); + } + }); + }); + this.model.setValue(device.model); + this.manufacturer.setValue(device.manufacturer); + this.mac_addr.setValue(device.mac_addr); + + if ( + device.test_pack && + (device.test_pack === TestingType.Qualification || + device.test_pack === TestingType.Pilot) + ) { + this.test_pack.setValue(device.test_pack); + } else { + this.test_pack.setValue(TestingType.Qualification); + } + + this.type?.setValue(device.type); + this.technology?.setValue(device.technology); + } + + private updateDevice(device: Device, callback: () => void) { + if (!this.data.isCreate && this.data.device) { + this.devicesStore.editDevice({ + device, + mac_addr: this.data.device.mac_addr, + onSuccess: callback, + }); + } else { + this.devicesStore.saveDevice({ device, onSuccess: callback }); + } + } + + private createDeviceFromForm(): Device { + const testModules: { [key: string]: { enabled: boolean } } = {}; + this.getStep(0).value.test_modules.forEach( + (enabled: boolean, i: number) => { + testModules[this.testModules[i]?.name] = { + enabled: enabled, + }; + } + ); + + const additionalInfo: Question[] = []; + + this.format.forEach(step => { + step.questions.forEach((question, index) => { + const response: Question = {}; + response.question = question.question; + + if (question.type === FormControlType.SELECT_MULTIPLE) { + const answer: number[] = []; + question.options?.forEach((_, idx) => { + const value = this.getStep(step.step).value[index][idx]; + if (value) { + answer.push(idx); + } + }); + response.answer = answer; + } else { + response.answer = this.getStep(step.step).value[index]?.trim(); + } + additionalInfo.push(response); + }); + }); + + return { + status: DeviceStatus.VALID, + model: this.model.value.trim(), + manufacturer: this.manufacturer.value.trim(), + mac_addr: this.mac_addr.value.trim(), + test_pack: this.test_pack.value, + test_modules: testModules, + type: this.type.value, + technology: this.technology.value, + additional_info: additionalInfo, + } as Device; + } + + private createBasicStep() { + const firstStep = this.fb.group({ + model: [ + '', + [ + this.profileValidators.textRequired(), + this.deviceValidators.deviceStringFormat(), + ], + ], + manufacturer: [ + '', + [ + this.profileValidators.textRequired(), + this.deviceValidators.deviceStringFormat(), + ], + ], + mac_addr: [ + '', + [ + this.profileValidators.textRequired(), + Validators.pattern(MAC_ADDRESS_PATTERN), + this.deviceValidators.differentMACAddress( + this.data.devices, + this.data.device + ), + ], + ], + test_modules: new FormArray( + [], + this.deviceValidators.testModulesRequired() + ), + test_pack: [TestingType.Qualification], + }); + + this.deviceQualificationForm = this.fb.group({ + steps: this.fb.array([firstStep]), + }); + } + + private createDeviceForm(format: DeviceQuestionnaireSection[]) { + format.forEach(() => { + (this.deviceQualificationForm.get('steps') as FormArray).controls.push( + this.createStep() + ); + }); + + // summary step + (this.deviceQualificationForm.get('steps') as FormArray).controls.push( + this.fb.group({}) + ); + } + + private createStep() { + return new FormGroup({}); + } + + private compareDevices(device1: Device, device2: Device) { + if (device1.manufacturer !== device2.manufacturer) { + return false; + } + if (device1.model !== device2.model) { + return false; + } + if (device1.mac_addr !== device2.mac_addr) { + return false; + } + if (device1.type !== device2.type) { + return false; + } + if (device1.technology !== device2.technology) { + return false; + } + if (device1.test_pack !== device2.test_pack) { + return false; + } + const keys1 = Object.keys(device1.test_modules!); + + for (const key of keys1) { + const val1 = device1.test_modules![key]; + const val2 = device2.test_modules![key]; + if (val1.enabled !== val2.enabled) { + return false; + } + } + + if (device1.additional_info) { + for (const question of device1.additional_info) { + if ( + question.answer !== + device2.additional_info?.find( + question2 => question2.question === question.question + )?.answer + ) { + return false; + } + } + } else { + return false; + } + return true; + } + + private deviceIsEmpty(device: Device) { + if (device.manufacturer !== '') { + return false; + } + if (device.model !== '') { + return false; + } + if (device.mac_addr !== '') { + return false; + } + if (device.type !== '') { + return false; + } + if (device.technology !== '') { + return false; + } + if (device.test_pack !== TestingType.Qualification) { + return false; + } + const keys1 = Object.keys(device.test_modules!); + + for (const key of keys1) { + const val1 = device.test_modules![key]; + if (!val1.enabled) { + return false; + } + } + + if (device.additional_info) { + for (const question of device.additional_info) { + if (question.answer !== '') { + return false; + } + } + } else { + return false; + } + return true; + } + + private setDialogHeight(): void { + const windowHeight = window.innerHeight; + if (windowHeight < this.FORM_HEIGHT) { + this.element.nativeElement.style.height = '100vh'; + } else { + this.element.nativeElement.style.height = this.FORM_HEIGHT + 'px'; + } + } +} diff --git a/modules/ui/src/app/pages/devices/devices-routing.module.ts b/modules/ui/src/app/pages/devices/devices-routing.module.ts index 19acb07c9..53ed9ef0a 100644 --- a/modules/ui/src/app/pages/devices/devices-routing.module.ts +++ b/modules/ui/src/app/pages/devices/devices-routing.module.ts @@ -16,8 +16,15 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { DevicesComponent } from './devices.component'; +import { CanDeactivateGuard } from '../../guards/can-deactivate.guard'; -const routes: Routes = [{ path: '', component: DevicesComponent }]; +const routes: Routes = [ + { + path: '', + component: DevicesComponent, + canDeactivate: [CanDeactivateGuard], + }, +]; @NgModule({ imports: [RouterModule.forChild(routes)], diff --git a/modules/ui/src/app/pages/devices/devices.component.html b/modules/ui/src/app/pages/devices/devices.component.html index aef3730c5..f6567b49e 100644 --- a/modules/ui/src/app/pages/devices/devices.component.html +++ b/modules/ui/src/app/pages/devices/devices.component.html @@ -20,14 +20,15 @@

Devices

- + { const openSpy = spyOn(component.dialog, 'open').and.returnValue({ afterClosed: () => of(true), - } as MatDialogRef); + beforeClosed: () => of(true), + } as MatDialogRef); fixture.detectChanges(); const button = compiled.querySelector( '.device-add-button' @@ -158,15 +150,18 @@ describe('DevicesComponent', () => { expect(button).toBeTruthy(); expect(openSpy).toHaveBeenCalled(); - expect(openSpy).toHaveBeenCalledWith(DeviceFormComponent, { - ariaLabel: 'Create device', + expect(openSpy).toHaveBeenCalledWith(DeviceQualificationFromComponent, { + ariaLabel: 'Create Device', data: { device: null, - title: 'Create device', + initialDevice: undefined, + title: 'Create Device', testModules: [], devices: [device, device, device], + index: 0, + isCreate: true, }, - autoFocus: true, + autoFocus: 'first-tabbable', hasBackdrop: true, disableClose: true, panelClass: 'device-form-dialog', @@ -178,47 +173,25 @@ describe('DevicesComponent', () => { describe('#openDialog', () => { it('should open device dialog on item click', () => { const openSpy = spyOn(component.dialog, 'open').and.returnValue({ - afterClosed: () => of(true), - } as MatDialogRef); + beforeClosed: () => of(true), + } as MatDialogRef); fixture.detectChanges(); - component.openDialog([device], MOCK_TEST_MODULES, device); + component.openDialog([device], MOCK_TEST_MODULES, device, device, true); expect(openSpy).toHaveBeenCalled(); - expect(openSpy).toHaveBeenCalledWith(DeviceFormComponent, { - ariaLabel: 'Edit device', - data: { - device: device, - title: 'Edit device', - devices: [device], - testModules: MOCK_TEST_MODULES, - }, - autoFocus: true, - hasBackdrop: true, - disableClose: true, - panelClass: 'device-form-dialog', - }); - - openSpy.calls.reset(); - }); - - it('should open device dialog with delete-button focus element', () => { - const openSpy = spyOn(component.dialog, 'open').and.returnValue({ - afterClosed: () => of(true), - } as MatDialogRef); - fixture.detectChanges(); - - component.openDialog([device], MOCK_TEST_MODULES, device, true); - - expect(openSpy).toHaveBeenCalledWith(DeviceFormComponent, { + expect(openSpy).toHaveBeenCalledWith(DeviceQualificationFromComponent, { ariaLabel: 'Edit device', data: { device: device, + initialDevice: device, title: 'Edit device', devices: [device], testModules: MOCK_TEST_MODULES, + index: 0, + isCreate: false, }, - autoFocus: '.delete-button', + autoFocus: 'first-tabbable', hasBackdrop: true, disableClose: true, panelClass: 'device-form-dialog', @@ -237,22 +210,67 @@ describe('DevicesComponent', () => { it('should call setIsOpenAddDevice if dialog closes with null', () => { spyOn(component.dialog, 'open').and.returnValue({ - afterClosed: () => of(null), - } as MatDialogRef); + beforeClosed: () => of(null), + } as MatDialogRef); component.openDialog([], MOCK_TEST_MODULES); expect(mockDevicesStore.setIsOpenAddDevice).toHaveBeenCalled(); }); + describe('close dialog', () => { + beforeEach(() => { + component.viewModel$ = of({ + devices: [device, device, device], + selectedDevice: device, + deviceInProgress: null, + testModules: [], + }); + fixture.detectChanges(); + }); + + it('should show device item', fakeAsync(() => { + const item = compiled.querySelectorAll('app-device-item'); + + expect(item.length).toEqual(3); + })); + + it('should open device dialog when dialog return null', () => { + const openDeviceDialogSpy = spyOn(component, 'openDialog'); + spyOn(component.dialog, 'open').and.returnValue({ + beforeClosed: () => of(null), + } as MatDialogRef); + + component.openCloseDialog( + [device], + MOCK_TEST_MODULES, + device, + undefined, + false, + 0, + 0 + ); + + expect(openDeviceDialogSpy).toHaveBeenCalledWith( + [device], + MOCK_TEST_MODULES, + device, + undefined, + false, + 0, + 0 + ); + }); + }); + it('should delete device if dialog closes with object, action delete and selected device', () => { spyOn(component.dialog, 'open').and.returnValue({ - afterClosed: () => + beforeClosed: () => of({ device, action: FormAction.Delete, }), - } as MatDialogRef); + } as MatDialogRef); component.openDialog([device], MOCK_TEST_MODULES, device); @@ -270,18 +288,20 @@ describe('DevicesComponent', () => { fixture.detectChanges(); }); - it('should show device item', fakeAsync(() => { - const item = compiled.querySelectorAll('app-device-item'); - - expect(item.length).toEqual(3); - })); - it('should delete device when dialog return true', () => { spyOn(component.dialog, 'open').and.returnValue({ - afterClosed: () => of(true), + beforeClosed: () => of(true), } as MatDialogRef); - component.openDeleteDialog([device], MOCK_TEST_MODULES, device); + component.openDeleteDialog( + [device], + MOCK_TEST_MODULES, + device, + device, + false, + 0, + 0 + ); const args = mockDevicesStore.deleteDevice.calls.argsFor(0); // @ts-expect-error config is in object @@ -292,16 +312,27 @@ describe('DevicesComponent', () => { it('should open device dialog when dialog return null', () => { const openDeviceDialogSpy = spyOn(component, 'openDialog'); spyOn(component.dialog, 'open').and.returnValue({ - afterClosed: () => of(null), + beforeClosed: () => of(null), } as MatDialogRef); - component.openDeleteDialog([device], MOCK_TEST_MODULES, device); + component.openDeleteDialog( + [device], + MOCK_TEST_MODULES, + device, + device, + false, + 0, + 0 + ); expect(openDeviceDialogSpy).toHaveBeenCalledWith( [device], MOCK_TEST_MODULES, device, - true + device, + false, + 0, + 0 ); }); }); @@ -322,17 +353,21 @@ describe('DevicesComponent', () => { device: device, testModules: MOCK_TEST_MODULES, }, - autoFocus: true, + autoFocus: 'dialog', hasBackdrop: true, disableClose: true, panelClass: 'initiate-test-run-dialog', }); - tick(); + tick(100); + expect(router.url).toBe(Routes.Testing); expect(mockDevicesStore.setStatus).toHaveBeenCalledWith( MOCK_PROGRESS_DATA_IN_PROGRESS ); + expect( + stateServiceMock.focusFirstElementInContainer + ).toHaveBeenCalled(); openSpy.calls.reset(); }); diff --git a/modules/ui/src/app/pages/devices/devices.component.ts b/modules/ui/src/app/pages/devices/devices.component.ts index be172aed3..6bf24431b 100644 --- a/modules/ui/src/app/pages/devices/devices.component.ts +++ b/modules/ui/src/app/pages/devices/devices.component.ts @@ -20,22 +20,36 @@ import { OnInit, ChangeDetectorRef, } from '@angular/core'; -import { MatDialog } from '@angular/material/dialog'; -import { Device, DeviceView, TestModule } from '../../model/device'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { - DeviceFormComponent, - FormAction, - FormResponse, -} from './components/device-form/device-form.component'; -import { Subject, takeUntil } from 'rxjs'; + Device, + DeviceStatus, + DeviceView, + TestModule, +} from '../../model/device'; +import { map, Subject, takeUntil, timer } from 'rxjs'; import { SimpleDialogComponent } from '../../components/simple-dialog/simple-dialog.component'; import { combineLatest } from 'rxjs/internal/observable/combineLatest'; import { FocusManagerService } from '../../services/focus-manager.service'; import { Routes } from '../../model/routes'; import { Router } from '@angular/router'; -import { timer } from 'rxjs/internal/observable/timer'; import { TestrunInitiateFormComponent } from '../testrun/components/testrun-initiate-form/testrun-initiate-form.component'; import { DevicesStore } from './devices.store'; +import { DeviceQualificationFromComponent } from './components/device-qualification-from/device-qualification-from.component'; +import { Observable } from 'rxjs/internal/Observable'; +import { CanComponentDeactivate } from '../../guards/can-deactivate.guard'; + +export enum FormAction { + Delete = 'Delete', + Close = 'Close', + Save = 'Save', +} + +export interface FormResponse { + device?: Device; + action: FormAction; + index: number; +} @Component({ selector: 'app-device-repository', @@ -43,10 +57,13 @@ import { DevicesStore } from './devices.store'; styleUrls: ['./devices.component.scss'], providers: [DevicesStore], }) -export class DevicesComponent implements OnInit, OnDestroy { +export class DevicesComponent + implements OnInit, OnDestroy, CanComponentDeactivate +{ readonly DeviceView = DeviceView; private destroy$: Subject = new Subject(); viewModel$ = this.devicesStore.viewModel$; + deviceDialog: MatDialogRef | undefined; constructor( private readonly focusManagerService: FocusManagerService, @@ -65,7 +82,11 @@ export class DevicesComponent implements OnInit, OnDestroy { ]) .pipe(takeUntil(this.destroy$)) .subscribe(([devices, isOpenAddDevice, testModules]) => { - if (!devices?.length && isOpenAddDevice) { + if ( + !devices?.filter(device => device.status === DeviceStatus.VALID) + .length && + isOpenAddDevice + ) { this.openDialog(devices, testModules); } }); @@ -76,6 +97,15 @@ export class DevicesComponent implements OnInit, OnDestroy { this.destroy$.unsubscribe(); } + canDeactivate(): Observable { + this.deviceDialog?.componentInstance?.closeForm(); + return this.dialog.afterAllClosed.pipe( + map(() => { + return true; + }) + ); + } + openStartTestrun( selectedDevice: Device, devices: Device[], @@ -88,7 +118,7 @@ export class DevicesComponent implements OnInit, OnDestroy { device: selectedDevice, testModules, }, - autoFocus: true, + autoFocus: 'dialog', hasBackdrop: true, disableClose: true, panelClass: 'initiate-test-run-dialog', @@ -104,7 +134,11 @@ export class DevicesComponent implements OnInit, OnDestroy { window.dataLayer.push({ event: 'successful_testrun_initiation', }); - this.route.navigate([Routes.Testing]); + this.route.navigate([Routes.Testing]).then(() => + timer(100).subscribe(() => { + this.focusManagerService.focusFirstElementInContainer(); + }) + ); } }); } @@ -112,61 +146,125 @@ export class DevicesComponent implements OnInit, OnDestroy { openDialog( devices: Device[] = [], testModules: TestModule[], + initialDevice?: Device, selectedDevice?: Device, - focusDeleteButton = false + isEditDevice = false, + index = 0, + deviceIndex?: number ): void { - const dialogRef = this.dialog.open(DeviceFormComponent, { - ariaLabel: selectedDevice ? 'Edit device' : 'Create device', + this.deviceDialog = this.dialog.open(DeviceQualificationFromComponent, { + ariaLabel: isEditDevice ? 'Edit device' : 'Create Device', data: { device: selectedDevice || null, - title: selectedDevice ? 'Edit device' : 'Create device', + initialDevice, + title: isEditDevice ? 'Edit device' : 'Create Device', testModules: testModules, devices, + index, + isCreate: !isEditDevice, }, - autoFocus: focusDeleteButton ? '.delete-button' : true, + autoFocus: 'first-tabbable', hasBackdrop: true, disableClose: true, panelClass: 'device-form-dialog', }); - - dialogRef - ?.afterClosed() - .pipe(takeUntil(this.destroy$)) - .subscribe((response: FormResponse) => { - this.devicesStore.selectDevice(null); - if (!response) { - this.devicesStore.setIsOpenAddDevice(false); - return; - } - if ( - response.action === FormAction.Save && - response.device && - !selectedDevice - ) { - timer(10) - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { + this.deviceDialog?.beforeClosed().subscribe((response: FormResponse) => { + if (!response) { + this.devicesStore.setIsOpenAddDevice(false); + return; + } + if (response.action === FormAction.Close) { + this.openCloseDialog( + devices, + testModules, + initialDevice, + response.device, + isEditDevice, + response.index, + deviceIndex + ); + } else if (response.action === FormAction.Save && response.device) { + timer(10) + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + if (!initialDevice) { this.focusManagerService.focusFirstElementInContainer(); - }); - } - if (response.action === FormAction.Delete && selectedDevice) { - this.devicesStore.selectDevice(selectedDevice); - this.openDeleteDialog(devices, testModules, selectedDevice); + } else if (deviceIndex !== undefined) { + this.focusSelectedButton(deviceIndex); + } + }); + } + if (response.action === FormAction.Delete && initialDevice) { + if (response.device) { + this.openDeleteDialog( + devices, + testModules, + initialDevice, + response.device, + isEditDevice, + response.index, + deviceIndex! + ); } - }); + } + }); + } + + openCloseDialog( + devices: Device[], + testModules: TestModule[], + initialDevice?: Device, + device?: Device, + isEditDevice = false, + index = 0, + deviceIndex?: number + ) { + const dialogRef = this.dialog.open(SimpleDialogComponent, { + ariaLabel: 'Close the Device menu', + data: { + title: 'Are you sure?', + content: `By closing the device profile you will loose any new changes you have made to the device.`, + }, + autoFocus: true, + hasBackdrop: true, + disableClose: true, + panelClass: 'simple-dialog', + }); + + dialogRef?.beforeClosed().subscribe(close => { + if (!close) { + this.openDialog( + devices, + testModules, + initialDevice, + device, + isEditDevice, + index, + deviceIndex + ); + } else if (deviceIndex !== undefined) { + this.focusSelectedButton(deviceIndex); + } else { + this.focusManagerService.focusFirstElementInContainer(); + } + }); } openDeleteDialog( devices: Device[], testModules: TestModule[], - device: Device + initialDevice: Device, + device: Device, + isEditDevice = false, + index = 0, + deviceIndex: number ) { const dialogRef = this.dialog.open(SimpleDialogComponent, { ariaLabel: 'Delete device', data: { title: 'Delete device?', content: `You are about to delete ${ - device.manufacturer + ' ' + device.model + initialDevice.manufacturer + ' ' + initialDevice.model }. Are you sure?`, device: device, }, @@ -175,35 +273,45 @@ export class DevicesComponent implements OnInit, OnDestroy { disableClose: true, panelClass: 'simple-dialog', }); - - dialogRef - ?.afterClosed() - .pipe(takeUntil(this.destroy$)) - .subscribe(deleteDevice => { - if (deleteDevice) { - this.devicesStore.deleteDevice({ - device, - onDelete: () => { - this.focusNextButton(); - this.devicesStore.selectDevice(null); - }, - }); - } else { - this.openDialog(devices, testModules, device, true); - this.devicesStore.selectDevice(null); - } - }); + dialogRef?.beforeClosed().subscribe(deleteDevice => { + if (deleteDevice) { + this.devicesStore.deleteDevice({ + device: initialDevice, + onDelete: () => { + this.focusNextButton(deviceIndex); + }, + }); + } else { + this.openDialog( + devices, + testModules, + initialDevice, + device, + isEditDevice, + index, + deviceIndex + ); + } + }); } - private focusNextButton() { - // Try to focus next device item, if exitst - const next = this.element.nativeElement.querySelector( - '.device-item-selected + app-device-item button' - ); + private focusSelectedButton(index: number) { + const selected = this.element.nativeElement.querySelectorAll( + 'app-device-item .button-edit' + )[index]; + if (selected) { + selected.focus(); + } + } + private focusNextButton(index: number) { + this.changeDetectorRef.detectChanges(); + // Try to focus next device item, if exist + const next = this.element.nativeElement.querySelectorAll( + 'app-device-item .button-edit' + )[index]; if (next) { next.focus(); } else { - this.changeDetectorRef.detectChanges(); // If next device item doest not exist, add device button should be focused const addButton = this.element.nativeElement.querySelector('.device-add-button'); diff --git a/modules/ui/src/app/pages/devices/devices.module.ts b/modules/ui/src/app/pages/devices/devices.module.ts index e0110eca3..32f8a2865 100644 --- a/modules/ui/src/app/pages/devices/devices.module.ts +++ b/modules/ui/src/app/pages/devices/devices.module.ts @@ -24,7 +24,6 @@ import { MatDialogModule } from '@angular/material/dialog'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatToolbarModule } from '@angular/material/toolbar'; -import { DeviceFormComponent } from './components/device-form/device-form.component'; import { DevicesRoutingModule } from './devices-routing.module'; import { DevicesComponent } from './devices.component'; @@ -35,7 +34,7 @@ import { SimpleDialogComponent } from '../../components/simple-dialog/simple-dia import { NgxMaskDirective, NgxMaskPipe, provideNgxMask } from 'ngx-mask'; @NgModule({ - declarations: [DevicesComponent, DeviceFormComponent], + declarations: [DevicesComponent], imports: [ CommonModule, DevicesRoutingModule, diff --git a/modules/ui/src/app/pages/devices/devices.store.ts b/modules/ui/src/app/pages/devices/devices.store.ts index 19586a1c4..c9d5e9523 100644 --- a/modules/ui/src/app/pages/devices/devices.store.ts +++ b/modules/ui/src/app/pages/devices/devices.store.ts @@ -34,11 +34,13 @@ import { setIsOpenAddDevice, } from '../../store/actions'; import { TestrunStatus } from '../../model/testrun-status'; +import { DeviceQuestionnaireSection } from '../../model/device'; export interface DevicesComponentState { devices: Device[]; selectedDevice: Device | null; testModules: TestModule[]; + questionnaireFormat: DeviceQuestionnaireSection[]; } @Injectable() @@ -46,10 +48,10 @@ export class DevicesStore extends ComponentStore { devices$ = this.store.select(selectDevices); isOpenAddDevice$ = this.store.select(selectIsOpenAddDevice); testModules$ = this.store.select(selectTestModules); + questionnaireFormat$ = this.select(state => state.questionnaireFormat); private deviceInProgress$ = this.store.select(selectDeviceInProgress); private selectedDevice$ = this.select(state => state.selectedDevice); - //testModules = this.testRunService.getTestModules(); viewModel$ = this.select({ devices: this.devices$, selectedDevice: this.selectedDevice$, @@ -62,6 +64,12 @@ export class DevicesStore extends ComponentStore { selectedDevice: device, })); + updateQuestionnaireFormat = this.updater( + (state, questionnaireFormat: DeviceQuestionnaireSection[]) => ({ + ...state, + questionnaireFormat, + }) + ); deleteDevice = this.effect<{ device: Device; onDelete: () => void; @@ -139,6 +147,18 @@ export class DevicesStore extends ComponentStore { ); }); + getQuestionnaireFormat = this.effect(trigger$ => { + return trigger$.pipe( + exhaustMap(() => { + return this.testRunService.fetchQuestionnaireFormat().pipe( + tap((questionnaireFormat: DeviceQuestionnaireSection[]) => { + this.updateQuestionnaireFormat(questionnaireFormat); + }) + ); + }) + ); + }); + private addDevice(device: Device, devices: Device[]): void { this.updateDevices(devices.concat([device])); } @@ -179,6 +199,7 @@ export class DevicesStore extends ComponentStore { devices: [], selectedDevice: null, testModules: [], + questionnaireFormat: [], }); } } diff --git a/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.html b/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.html index 7b074ec54..8897dbc75 100644 --- a/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.html +++ b/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.html @@ -18,7 +18,9 @@ class="delete-report-button" href="#" matTooltip="Delete report for Testrun # {{ getTestRunId(data) }}" - (click)="deleteReport($event)"> + (click)="deleteReport($event)" + (keydown.enter)="deleteReport($event)" + (keydown.space)="deleteReport($event)"> diff --git a/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.scss b/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.scss index 6b65c0a82..3041ab183 100644 --- a/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.scss +++ b/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.scss @@ -40,7 +40,7 @@ min-width: 38px; margin: 0; padding: 0 8px; - color: mat.get-color-from-palette($color-primary, 600); + color: mat.m2-get-color-from-palette($color-primary, 600); font-weight: 500; line-height: 20px; letter-spacing: 0.25px; diff --git a/modules/ui/src/app/pages/reports/reports.component.html b/modules/ui/src/app/pages/reports/reports.component.html index 6fc7017a8..beb538149 100644 --- a/modules/ui/src/app/pages/reports/reports.component.html +++ b/modules/ui/src/app/pages/reports/reports.component.html @@ -44,7 +44,7 @@

Reports

context: { name: FilterName.Started, filterOpened: vm.filterOpened, - activeFilter: vm.activeFilter + activeFilter: vm.activeFilter, } ">
@@ -82,7 +82,7 @@

Reports

context: { name: FilterName.DeviceInfo, filterOpened: vm.filterOpened, - activeFilter: vm.activeFilter + activeFilter: vm.activeFilter, } "> @@ -104,7 +104,7 @@

Reports

context: { name: FilterName.DeviceFirmware, filterOpened: vm.filterOpened, - activeFilter: vm.activeFilter + activeFilter: vm.activeFilter, } "> @@ -112,6 +112,20 @@

Reports

{{ data.deviceFirmware }} + + + + Assessment type + + + {{ data.program }} + + + Reports context: { name: FilterName.Results, filterOpened: vm.filterOpened, - activeFilter: vm.activeFilter + activeFilter: vm.activeFilter, } "> @@ -204,7 +218,7 @@

Reports

context: { header: 'Sorry, there are no reports matching the search criteria.', - message: 'Please consider change or clear filters.' + message: 'Please consider change or clear filters.', } ">
@@ -216,7 +230,9 @@

Reports

mat-row #rowElement [class.report-selected]="rowElement === vm.selectedRow" - (click)="selectRow(rowElement)"> + (click)="selectRow(rowElement)" + (keydown.enter)="selectRow(rowElement)" + (keydown.space)="selectRow(rowElement)">
@@ -231,7 +247,7 @@

Reports

context: { header: 'Sorry, there are no reports yet!', message: - 'Reports will automatically generate following a test attempt completion.' + 'Reports will automatically generate following a test attempt completion.', } ">
diff --git a/modules/ui/src/app/pages/reports/reports.component.scss b/modules/ui/src/app/pages/reports/reports.component.scss index 47d76d880..bea1c4b00 100644 --- a/modules/ui/src/app/pages/reports/reports.component.scss +++ b/modules/ui/src/app/pages/reports/reports.component.scss @@ -94,7 +94,7 @@ } .filter-button.active .mat-icon { - color: mat.get-color-from-palette($color-primary, 600); + color: mat.m2-get-color-from-palette($color-primary, 600); } } @@ -124,10 +124,12 @@ display: flex; align-items: center; justify-content: center; - grid-row: 1/3; } .results-content-empty { + position: absolute; + top: 0; + width: 100%; height: 100%; } diff --git a/modules/ui/src/app/pages/reports/reports.component.spec.ts b/modules/ui/src/app/pages/reports/reports.component.spec.ts index c59cbedfb..4bcf2ebf3 100644 --- a/modules/ui/src/app/pages/reports/reports.component.spec.ts +++ b/modules/ui/src/app/pages/reports/reports.component.spec.ts @@ -13,7 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing'; +import { + ComponentFixture, + fakeAsync, + TestBed, + tick, +} from '@angular/core/testing'; import { ReportsComponent } from './reports.component'; import { TestRunService } from '../../services/test-run.service'; @@ -247,7 +252,7 @@ describe('ReportsComponent', () => { fixture.detectChanges(); }); - it('should focus next active element if exist', () => { + it('should focus next active element if exist', fakeAsync(() => { const row = window.document.querySelector('tbody tr') as HTMLElement; row.classList.add('report-selected'); const nextButton = window.document.querySelector( @@ -257,10 +262,12 @@ describe('ReportsComponent', () => { component.focusNextButton(); + tick(50); + expect(buttonFocusSpy).toHaveBeenCalled(); - }); + })); - it('should focus navigation button if next active element does not exist', () => { + it('should focus navigation button if next active element does not exist', fakeAsync(() => { const button = document.createElement('BUTTON'); button.classList.add('app-sidebar-button-reports'); document.querySelector('body')?.appendChild(button); @@ -268,8 +275,10 @@ describe('ReportsComponent', () => { component.focusNextButton(); + tick(50); + expect(buttonFocusSpy).toHaveBeenCalled(); - }); + })); }); it('#removeDevice should call delete report', () => { @@ -323,6 +332,7 @@ describe('ReportsComponent', () => { green: false, red: true, blue: false, + cyan: false, grey: false, }); component.ngOnInit(); diff --git a/modules/ui/src/app/pages/reports/reports.component.ts b/modules/ui/src/app/pages/reports/reports.component.ts index 32c171ff6..f2cba227d 100644 --- a/modules/ui/src/app/pages/reports/reports.component.ts +++ b/modules/ui/src/app/pages/reports/reports.component.ts @@ -28,7 +28,7 @@ import { } from '../../model/testrun-status'; import { DatePipe } from '@angular/common'; import { MatSort, Sort } from '@angular/material/sort'; -import { Subject, takeUntil } from 'rxjs'; +import { Subject, takeUntil, timer } from 'rxjs'; import { MatRow } from '@angular/material/table'; import { FilterDialogComponent } from './components/filter-dialog/filter-dialog.component'; import { MatDialog } from '@angular/material/dialog'; @@ -152,13 +152,17 @@ export class ReportsComponent implements OnInit, OnDestroy { '.report-selected + tr a' ) as HTMLButtonElement; if (next) { - next.focus(); + timer(50).subscribe(() => { + next.focus(); + }); } else { // If next interactive element doest not exist, add menu reports button should be focused const menuButton = window.document.querySelector( '.app-sidebar-button-reports' ) as HTMLButtonElement; - menuButton?.focus(); + timer(50).subscribe(() => { + menuButton?.focus(); + }); } } diff --git a/modules/ui/src/app/pages/reports/reports.store.spec.ts b/modules/ui/src/app/pages/reports/reports.store.spec.ts index 6e6d1656a..e6b7ec2f3 100644 --- a/modules/ui/src/app/pages/reports/reports.store.spec.ts +++ b/modules/ui/src/app/pages/reports/reports.store.spec.ts @@ -136,6 +136,7 @@ describe('ReportsStore', () => { 'duration', 'deviceInfo', 'deviceFirmware', + 'program', 'status', 'report', ], diff --git a/modules/ui/src/app/pages/reports/reports.store.ts b/modules/ui/src/app/pages/reports/reports.store.ts index 39db74fd2..7b13bf5e9 100644 --- a/modules/ui/src/app/pages/reports/reports.store.ts +++ b/modules/ui/src/app/pages/reports/reports.store.ts @@ -253,6 +253,7 @@ export class ReportsStore extends ComponentStore { deviceFirmware: item.device.firmware, deviceInfo: item.device.manufacturer + ' ' + item.device.model, duration: this.getDuration(item.started, item.finished), + program: item.device.test_pack ?? '', }; }); } @@ -367,6 +368,7 @@ export class ReportsStore extends ComponentStore { 'duration', 'deviceInfo', 'deviceFirmware', + 'program', 'status', 'report', ], diff --git a/modules/ui/src/app/pages/reports/reportscomponent.ts b/modules/ui/src/app/pages/reports/reportscomponent.ts deleted file mode 100644 index 32c171ff6..000000000 --- a/modules/ui/src/app/pages/reports/reportscomponent.ts +++ /dev/null @@ -1,173 +0,0 @@ -/** - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { - Component, - ElementRef, - OnDestroy, - OnInit, - ViewChild, -} from '@angular/core'; -import { LiveAnnouncer } from '@angular/cdk/a11y'; -import { TestRunService } from '../../services/test-run.service'; -import { - StatusResultClassName, - TestrunStatus, -} from '../../model/testrun-status'; -import { DatePipe } from '@angular/common'; -import { MatSort, Sort } from '@angular/material/sort'; -import { Subject, takeUntil } from 'rxjs'; -import { MatRow } from '@angular/material/table'; -import { FilterDialogComponent } from './components/filter-dialog/filter-dialog.component'; -import { MatDialog } from '@angular/material/dialog'; -import { tap } from 'rxjs/internal/operators/tap'; -import { FilterName, Filters } from '../../model/filters'; -import { ReportsStore } from './reports.store'; - -@Component({ - selector: 'app-history', - templateUrl: './reports.component.html', - styleUrls: ['./reports.component.scss'], - providers: [ReportsStore], -}) -export class ReportsComponent implements OnInit, OnDestroy { - public readonly FilterName = FilterName; - private destroy$: Subject = new Subject(); - @ViewChild(MatSort, { static: false }) sort!: MatSort; - viewModel$ = this.store.viewModel$; - constructor( - private testRunService: TestRunService, - private datePipe: DatePipe, - private liveAnnouncer: LiveAnnouncer, - public dialog: MatDialog, - private store: ReportsStore - ) {} - - ngOnInit() { - this.store.getReports(); - this.store.updateSort(this.sort); - } - - getFormattedDateString(date: string | null) { - return date ? this.datePipe.transform(date, 'd MMM y H:mm') : ''; - } - sortData(sortState: Sort) { - this.store.updateSort(this.sort); - if (sortState.direction) { - this.liveAnnouncer.announce(`Sorted ${sortState.direction}ending`); - } else { - this.liveAnnouncer.announce('Sorting cleared'); - } - } - - public getResultClass(status: string): StatusResultClassName { - return this.testRunService.getResultClass(status); - } - - openFilter(event: Event, filter: string, filterOpened: boolean) { - event.stopPropagation(); - const target = new ElementRef(event.currentTarget); - - if (!filterOpened) { - this.openFilterDialog(target, filter); - } - } - - openFilterDialog(target: ElementRef, filter: string) { - this.store.setFilterOpened(true); - this.store.setActiveFiler(filter); - const dialogRef = this.dialog.open(FilterDialogComponent, { - ariaLabel: 'Filters', - data: { - filter, - trigger: target, - }, - autoFocus: true, - hasBackdrop: true, - disableClose: true, - panelClass: 'filter-form-dialog', - }); - - dialogRef - ?.afterClosed() - .pipe( - takeUntil(this.destroy$), - tap(() => { - this.store.setFilterOpened(false); - this.store.setActiveFiler(''); - }) - ) - .subscribe(filteredData => { - if (filteredData) { - if (filter === FilterName.Results) { - this.store.setFilteredValuesResults(filteredData.results); - } - if (filter === FilterName.DeviceInfo) { - this.store.setFilteredValuesDeviceInfo(filteredData.deviceInfo); - } - if (filter === FilterName.DeviceFirmware) { - this.store.setFilteredValuesDeviceFirmware( - filteredData.deviceFirmware - ); - } - if (filter === FilterName.Started) { - this.store.setFilteredValuesDateRange(filteredData.dateRange); - } - } - }); - } - - filterCleared(filters: Filters) { - this.store.setFilteredValues(filters); - } - - ngOnDestroy() { - this.destroy$.next(true); - this.destroy$.unsubscribe(); - } - - selectRow(row: MatRow) { - this.store.setSelectedRow(row); - } - - trackByStarted(index: number, item: TestrunStatus) { - return item.started; - } - - focusNextButton() { - // Try to focus next interactive element, if exists - const next = window.document.querySelector( - '.report-selected + tr a' - ) as HTMLButtonElement; - if (next) { - next.focus(); - } else { - // If next interactive element doest not exist, add menu reports button should be focused - const menuButton = window.document.querySelector( - '.app-sidebar-button-reports' - ) as HTMLButtonElement; - menuButton?.focus(); - } - } - - removeDevice(data: TestrunStatus) { - this.store.deleteReport({ - mac_addr: data.mac_addr, - deviceMacAddr: data.device.mac_addr, - started: data.started, - }); - this.focusNextButton(); - } -} diff --git a/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.html b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.html new file mode 100644 index 000000000..debcfa36c --- /dev/null +++ b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.html @@ -0,0 +1,45 @@ + +Risk Assessment Profile Completed +

+ It has been saved as "{{ data.profile.name }}" and can now be attached to + reports. +

+

+ The preliminary risk estimation based on your answers is + + {{ data.profile.risk }} risk + + +
{{ getRiskExplanation(data.profile.risk) }} The full report can be found + in the zip file. Please share with the lab to validate this profile and + determine next steps. +

+ + + diff --git a/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.scss b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.scss new file mode 100644 index 000000000..23badf7a4 --- /dev/null +++ b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.scss @@ -0,0 +1,73 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import '../../../../../theming/colors'; +@import '../../../../../theming/variables'; + +:host { + display: grid; + overflow: hidden; + width: 570px; + padding: 24px 0 8px 0; + > * { + padding: 0 16px 0 24px; + } +} + +.simple-dialog-title { + font-family: $font-primary; + font-size: 18px; + font-weight: 400; + line-height: 24px; + text-align: left; +} + +.simple-dialog-title + .simple-dialog-content { + margin-top: 0; + padding-top: 0; + border-bottom: 1px solid $lighter-grey; +} + +.simple-dialog-content { + font-family: Roboto, sans-serif; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.2px; + color: $grey-800; + padding: 16px 16px 16px 24px; + margin: 0; +} + +.simple-dialog-actions { + padding: 0; + min-height: 30px; +} + +.simple-dialog-content-risk { + font-weight: bold; + display: inline-flex; + align-items: center; +} + +.profile-item-risk { + display: inline-flex; + align-items: center; + height: 20px; + margin-left: 2px; + font-family: $font-secondary; + font-size: 12px; + font-weight: 400; + letter-spacing: 0.3px; +} diff --git a/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.spec.ts b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.spec.ts new file mode 100644 index 000000000..b3a40c1bc --- /dev/null +++ b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.spec.ts @@ -0,0 +1,78 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SuccessDialogComponent } from './success-dialog.component'; +import { TestRunService } from '../../../../services/test-run.service'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { of } from 'rxjs'; +import { PROFILE_MOCK } from '../../../../mocks/profile.mock'; +import { ProfileRisk } from '../../../../model/profile'; + +describe('SuccessDialogComponent', () => { + let component: SuccessDialogComponent; + let fixture: ComponentFixture; + const testRunServiceMock = jasmine.createSpyObj(['getRiskClass']); + let compiled: HTMLElement; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SuccessDialogComponent], + providers: [ + { provide: TestRunService, useValue: testRunServiceMock }, + { + provide: MatDialogRef, + useValue: { + keydownEvents: () => of(new KeyboardEvent('keydown', { code: '' })), + close: () => ({}), + }, + }, + { provide: MAT_DIALOG_DATA, useValue: {} }, + ], + }).compileComponents(); + fixture = TestBed.createComponent(SuccessDialogComponent); + component = fixture.componentInstance; + component.data = { + profile: PROFILE_MOCK, + }; + compiled = fixture.nativeElement as HTMLElement; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should close dialog on "cancel" click', () => { + const closeSpy = spyOn(component.dialogRef, 'close'); + const confirmButton = compiled.querySelector( + '.confirm-button' + ) as HTMLButtonElement; + + confirmButton?.click(); + + expect(closeSpy).toHaveBeenCalled(); + + closeSpy.calls.reset(); + }); + + it('should return proper text for risk', () => { + expect(component.getRiskExplanation(ProfileRisk.LIMITED)).toEqual(''); + expect(component.getRiskExplanation(ProfileRisk.HIGH)).toEqual( + 'An additional assessment may be required.' + ); + }); +}); diff --git a/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.ts b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.ts new file mode 100644 index 000000000..e7ce23ff3 --- /dev/null +++ b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.ts @@ -0,0 +1,65 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Component, Inject } from '@angular/core'; +import { + MAT_DIALOG_DATA, + MatDialogModule, + MatDialogRef, +} from '@angular/material/dialog'; +import { MatButtonModule } from '@angular/material/button'; +import { EscapableDialogComponent } from '../../../../components/escapable-dialog/escapable-dialog.component'; +import { + Profile, + ProfileRisk, + RiskResultClassName, +} from '../../../../model/profile'; +import { TestRunService } from '../../../../services/test-run.service'; +import { CommonModule } from '@angular/common'; + +interface DialogData { + profile: Profile; +} + +@Component({ + selector: 'app-success-dialog', + templateUrl: './success-dialog.component.html', + styleUrls: ['./success-dialog.component.scss'], + standalone: true, + imports: [MatDialogModule, MatButtonModule, CommonModule], +}) +export class SuccessDialogComponent extends EscapableDialogComponent { + constructor( + private readonly testRunService: TestRunService, + public override dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: DialogData + ) { + super(dialogRef); + } + + confirm() { + this.dialogRef.close(); + } + + public getRiskClass(riskResult: string): RiskResultClassName { + return this.testRunService.getRiskClass(riskResult); + } + + getRiskExplanation(risk: string | undefined) { + return risk === ProfileRisk.HIGH + ? 'An additional assessment may be required.' + : ''; + } +} diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html index 50f0bf3c7..dc1b0a330 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html @@ -15,11 +15,11 @@ -->
-

Profile name *

+

Profile name *

+ class="profile-form-field"> Specify risk assessment profile name Required for saving a profile @@ -43,21 +43,7 @@ - - - +
@@ -92,239 +78,3 @@ Close
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{ description }} - - Please, check. “ and \ are not allowed. - - - The field is required - - - The field must be a maximum of - {{ getControl(formControlName).getError('maxlength').requiredLength }} - characters. - - - - - - - - {{ description }} - - Please, check. “ and \ are not allowed. - - - The field is required - - - The field must be a maximum of - {{ getControl(formControlName).getError('maxlength').requiredLength }} - characters. - - - - - - - - {{ description }} - - The field is required - - - Please, check the email address. Valid e-mail can contain only latin - letters, numbers, @ and . (dot). - - - The field must be a maximum of - {{ getControl(formControlName).getError('maxlength').requiredLength }} - characters. - - - - - -
-

- - {{ option }} - -

- {{ - description - }} -
-
- - - - - - {{ option }} - - - {{ - description - }} - - The field is required - - - diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.scss b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.scss index 4fed0e420..1e4ad721b 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.scss +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.scss @@ -25,41 +25,20 @@ .profile-form { overflow: scroll; + + .name-field-label { + padding-top: 0; + } .field-container { display: flex; flex-direction: column; align-items: flex-start; padding: 8px 16px 8px 24px; } - .field-label { - margin: 0; - color: $grey-800; - font-size: 18px; - line-height: 24px; - padding-top: 24px; - padding-bottom: 16px; - &:first-child { - padding-top: 0; - } - &:has(+ .field-select-multiple.ng-invalid.ng-dirty) { - color: mat.get-color-from-palette($color-warn, 700); - } - } - mat-form-field { + + .profile-form-field { width: 100%; } - .field-hint { - font-family: $font-secondary; - font-size: 12px; - font-weight: 400; - line-height: 16px; - text-align: left; - padding-top: 8px; - } -} - -.profile-form-field { - width: 100%; } .profile-form-field ::ng-deep .mat-mdc-form-field-textarea-control { @@ -77,19 +56,9 @@ color: $primary; } -.field-select-multiple { - .field-select-checkbox { - &:has(::ng-deep .mat-mdc-checkbox-checked) { - background: mat.get-color-from-palette($color-primary, 50); - } - ::ng-deep .mdc-checkbox__ripple { - display: none; - } - &:first-of-type { - margin-top: 0; - } - &:last-of-type { - margin-bottom: 8px; - } - } +.save-profile-button:not(.mat-mdc-button-disabled), +.save-draft-button:not(.mat-mdc-button-disabled), +.discard-button:not(.mat-mdc-button-disabled) { + cursor: pointer; + pointer-events: auto; } diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts index 54a87e3b9..237f09160 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts @@ -28,7 +28,7 @@ import { PROFILE_MOCK_3, RENAME_PROFILE_MOCK, } from '../../../mocks/profile.mock'; -import { FormControlType, ProfileStatus } from '../../../model/profile'; +import { ProfileStatus } from '../../../model/profile'; describe('ProfileFormComponent', () => { let component: ProfileFormComponent; @@ -146,175 +146,6 @@ describe('ProfileFormComponent', () => { }); }); - PROFILE_FORM.forEach((item, index) => { - const uiIndex = index + 1; // as Profile name is at 0 position, the json items start from 1 i - - it(`should have form field with specific type"`, () => { - const fields = compiled.querySelectorAll('.profile-form-field'); - - if (item.type === FormControlType.SELECT) { - const select = fields[uiIndex].querySelector('mat-select'); - expect(select).toBeTruthy(); - } else if (item.type === FormControlType.SELECT_MULTIPLE) { - const select = fields[uiIndex].querySelector('mat-checkbox'); - expect(select).toBeTruthy(); - } else if (item.type === FormControlType.TEXTAREA) { - const input = fields[uiIndex]?.querySelector('textarea'); - expect(input).toBeTruthy(); - } else { - const input = fields[uiIndex]?.querySelector('input'); - expect(input).toBeTruthy(); - } - }); - - it('should have label', () => { - const labels = compiled.querySelectorAll('.field-label'); - const uiIndex = index + 1; // as Profile name is at 0 position, the json items start from 1 i - - const label = item?.validation?.required - ? item.question + ' *' - : item.question; - expect(labels[uiIndex].textContent?.trim()).toEqual(label); - }); - - it('should have hint', () => { - const fields = compiled.querySelectorAll('.profile-form-field'); - const uiIndex = index + 1; // as Profile name is at 0 position, the json items start from 1 i - const hint = fields[uiIndex].querySelector('mat-hint'); - - if (item.description) { - expect(hint?.textContent?.trim()).toEqual(item.description); - } else { - expect(hint).toBeNull(); - } - }); - - if (item.type === FormControlType.SELECT) { - describe('select', () => { - it(`should have default value if provided`, () => { - const fields = compiled.querySelectorAll('.profile-form-field'); - const select = fields[uiIndex].querySelector('mat-select'); - expect(select?.textContent?.trim()).toEqual(item.default || ''); - }); - - it('should have "required" error when field is not filled', () => { - const fields = compiled.querySelectorAll('.profile-form-field'); - - component.getControl(index).setValue(''); - component.getControl(index).markAsTouched(); - - fixture.detectChanges(); - - const error = fields[uiIndex].querySelector('mat-error')?.innerHTML; - - expect(error).toContain('The field is required'); - }); - }); - } - - if (item.type === FormControlType.SELECT_MULTIPLE) { - describe('select multiple', () => { - it(`should mark form group as dirty while tab navigation`, () => { - const fields = compiled.querySelectorAll('.profile-form-field'); - const checkbox = fields[uiIndex].querySelector( - '.field-select-checkbox:last-of-type mat-checkbox' - ); - checkbox?.dispatchEvent( - new KeyboardEvent('keydown', { key: 'Tab' }) - ); - fixture.detectChanges(); - - expect(component.getControl(index).dirty).toBeTrue(); - }); - }); - } - - if ( - item.type === FormControlType.TEXT || - item.type === FormControlType.TEXTAREA || - item.type === FormControlType.EMAIL_MULTIPLE - ) { - describe('text or text-long or email-multiple', () => { - if (item.validation?.required) { - it('should have "required" error when field is not filled', () => { - const fields = compiled.querySelectorAll('.profile-form-field'); - const uiIndex = index + 1; // as Profile name is at 0 position, the json items start from 1 i - const input = fields[uiIndex].querySelector( - '.mat-mdc-input-element' - ) as HTMLInputElement; - ['', ' '].forEach(value => { - input.value = value; - input.dispatchEvent(new Event('input')); - component.getControl(index).markAsTouched(); - fixture.detectChanges(); - const errors = fields[uiIndex].querySelectorAll('mat-error'); - let hasError = false; - errors.forEach(error => { - if (error.textContent === 'The field is required') { - hasError = true; - } - }); - - expect(hasError).toBeTrue(); - }); - }); - } - - it('should have "invalid_format" error when field does not satisfy validation rules', () => { - const fields = compiled.querySelectorAll('.profile-form-field'); - const uiIndex = index + 1; // as Profile name is at 0 position, the json items start from 1 i - const input: HTMLInputElement = fields[uiIndex].querySelector( - '.mat-mdc-input-element' - ) as HTMLInputElement; - input.value = 'as\\\\\\\\\\""""""""'; - input.dispatchEvent(new Event('input')); - component.getControl(index).markAsTouched(); - fixture.detectChanges(); - const result = - item.type === FormControlType.EMAIL_MULTIPLE - ? 'Please, check the email address. Valid e-mail can contain only latin letters, numbers, @ and . (dot).' - : 'Please, check. “ and \\ are not allowed.'; - const errors = fields[uiIndex].querySelectorAll('mat-error'); - let hasError = false; - errors.forEach(error => { - if (error.textContent === result) { - hasError = true; - } - }); - - expect(hasError).toBeTrue(); - }); - - if (item.validation?.max) { - it('should have "maxlength" error when field is exceeding max length', () => { - const fields = compiled.querySelectorAll('.profile-form-field'); - const uiIndex = index + 1; // as Profile name is at 0 position, the json items start from 1 i - const input: HTMLInputElement = fields[uiIndex].querySelector( - '.mat-mdc-input-element' - ) as HTMLInputElement; - input.value = - 'very long value very long value very long value very long value very long value very long value very long value very long value very long value very long value'; - input.dispatchEvent(new Event('input')); - component.getControl(index).markAsTouched(); - fixture.detectChanges(); - - const errors = fields[uiIndex].querySelectorAll('mat-error'); - let hasError = false; - errors.forEach(error => { - if ( - error.textContent === - `The field must be a maximum of ${item.validation?.max} characters.` - ) { - hasError = true; - } - }); - expect(hasError).toBeTrue(); - }); - } - }); - } - }); - describe('Draft button', () => { it('should be disabled when profile name is empty', () => { component.nameControl.setValue(''); diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts index 567eb6c34..fe57d8867 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts @@ -16,6 +16,7 @@ import { CdkTextareaAutosize, TextFieldModule } from '@angular/cdk/text-field'; import { afterNextRender, + AfterViewInit, ChangeDetectionStrategy, Component, EventEmitter, @@ -39,19 +40,19 @@ import { FormGroup, ReactiveFormsModule, ValidatorFn, - Validators, } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { DeviceValidators } from '../../devices/components/device-form/device.validators'; import { - FormControlType, Profile, ProfileFormat, ProfileStatus, Question, - Validation, } from '../../../model/profile'; +import { FormControlType } from '../../../model/question'; import { ProfileValidators } from './profile.validators'; +import { DynamicFormComponent } from '../../../components/dynamic-form/dynamic-form.component'; +import { CdkTrapFocus } from '@angular/cdk/a11y'; @Component({ selector: 'app-profile-form', @@ -66,17 +67,18 @@ import { ProfileValidators } from './profile.validators'; MatSelectModule, MatCheckboxModule, TextFieldModule, + DynamicFormComponent, ], templateUrl: './profile-form.component.html', styleUrl: './profile-form.component.scss', + hostDirectives: [CdkTrapFocus], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ProfileFormComponent implements OnInit { +export class ProfileFormComponent implements OnInit, AfterViewInit { private profile: Profile | null = null; private profileList!: Profile[]; private injector = inject(Injector); private nameValidator!: ValidatorFn; - public readonly FormControlType = FormControlType; public readonly ProfileStatus = ProfileStatus; profileForm: FormGroup = this.fb.group({}); @ViewChildren(CdkTextareaAutosize) @@ -112,9 +114,12 @@ export class ProfileFormComponent implements OnInit { private fb: FormBuilder ) {} ngOnInit() { - this.profileForm = this.createProfileForm(this.profileFormat); + this.profileForm = this.createProfileForm(); + } + + ngAfterViewInit(): void { if (this.selectedProfile) { - this.fillProfileForm(this.profileFormat, this.selectedProfile); + this.fillProfileForm(this.profileFormat, this.selectedProfile!); } } @@ -139,7 +144,7 @@ export class ProfileFormComponent implements OnInit { return this.profileForm.get(name.toString()) as AbstractControl; } - createProfileForm(questions: ProfileFormat[]): FormGroup { + createProfileForm(): FormGroup { // eslint-disable-next-line @typescript-eslint/no-explicit-any const group: any = {}; @@ -154,52 +159,9 @@ export class ProfileFormComponent implements OnInit { this.nameValidator, ]); - questions.forEach((question, index) => { - if (question.type === FormControlType.SELECT_MULTIPLE) { - group[index] = this.getMultiSelectGroup(question); - } else { - const validators = this.getValidators( - question.type, - question.validation - ); - group[index] = new FormControl(question.default || '', validators); - } - }); return new FormGroup(group); } - getValidators(type: FormControlType, validation?: Validation): ValidatorFn[] { - const validators: ValidatorFn[] = []; - if (validation) { - if (validation.required) { - validators.push(this.profileValidators.textRequired()); - } - if (validation.max) { - validators.push(Validators.maxLength(Number(validation.max))); - } - if (type === FormControlType.EMAIL_MULTIPLE) { - validators.push(this.profileValidators.emailStringFormat()); - } - if (type === FormControlType.TEXT || type === FormControlType.TEXTAREA) { - validators.push(this.profileValidators.textFormat()); - } - } - return validators; - } - - getMultiSelectGroup(question: ProfileFormat): FormGroup { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const group: any = {}; - question.options?.forEach((option, index) => { - group[index] = false; - }); - return this.fb.group(group, { - validators: question.validation?.required - ? [this.profileValidators.multiSelectRequired] - : [], - }); - } - getFormGroup(name: string | number): FormGroup { return this.profileForm?.controls[name] as FormGroup; } @@ -236,16 +198,6 @@ export class ProfileFormComponent implements OnInit { this.saveProfile.emit(response); } - public markSectionAsDirty( - optionIndex: number, - optionLength: number, - formControlName: string - ) { - if (optionIndex === optionLength - 1) { - this.getControl(formControlName).markAsDirty(); - } - } - onDiscardClick() { this.discard.emit(); } diff --git a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.html b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.html index 31049cd93..41f90de38 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.html +++ b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.html @@ -28,8 +28,10 @@ ? EXPIRED_TOOLTIP : profile.status }}" + [attr.aria-label]="getProfileItemLabel(profile)" (click)="profileClicked.emit(profile)" - (keydown.enter)="enterProfileItem(profile)"> + (keydown.enter)="enterProfileItem(profile)" + (keydown.space)="enterProfileItem(profile)"> + (click)="copyProfileClicked.emit(profile)" + (keydown.enter)="copyProfileClicked.emit(profile)" + (keydown.space)="copyProfileClicked.emit(profile)"> content_copy diff --git a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.ts b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.ts index 514cbd46e..6ddbe06e9 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.ts @@ -29,7 +29,7 @@ import { } from '../../../model/profile'; import { MatIcon } from '@angular/material/icon'; import { MatButtonModule } from '@angular/material/button'; -import { CommonModule } from '@angular/common'; +import { CommonModule, DatePipe } from '@angular/common'; import { TestRunService } from '../../../services/test-run.service'; import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip'; import { LiveAnnouncer } from '@angular/cdk/a11y'; @@ -38,7 +38,7 @@ import { LiveAnnouncer } from '@angular/cdk/a11y'; selector: 'app-profile-item', standalone: true, imports: [MatIcon, MatButtonModule, CommonModule, MatTooltipModule], - providers: [MatTooltip], + providers: [MatTooltip, DatePipe], templateUrl: './profile-item.component.html', styleUrl: './profile-item.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, @@ -63,7 +63,8 @@ export class ProfileItemComponent { constructor( private readonly testRunService: TestRunService, - private liveAnnouncer: LiveAnnouncer + private liveAnnouncer: LiveAnnouncer, + private datePipe: DatePipe ) {} public getRiskClass(riskResult: string): RiskResultClassName { @@ -82,4 +83,13 @@ export class ProfileItemComponent { this.profileClicked.emit(profile); } } + + getProfileItemLabel(profile: Profile) { + return `${profile.status} ${profile.risk} risk ${profile.name} ${this.datePipe.transform(profile.created, 'dd MMM yyyy')}`; + } + + delete(event: Event, name: string) { + event.preventDefault(); + this.deleteButtonClicked.emit(name); + } } diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss index c7241c6c4..90f8a3a41 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss @@ -22,6 +22,8 @@ } .risk-assessment-content-empty { + position: absolute; + top: 0; height: 100%; width: calc(100%); display: flex; diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts index 8e792ff83..0b6d70210 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts @@ -48,6 +48,7 @@ describe('RiskAssessmentComponent', () => { const mockLiveAnnouncer: SpyObj = jasmine.createSpyObj([ 'announce', + 'clear', ]); let compiled: HTMLElement; @@ -171,15 +172,13 @@ describe('RiskAssessmentComponent', () => { tick(); expect(openSpy).toHaveBeenCalledWith(SimpleDialogComponent, { - ariaLabel: 'Delete risk profile', data: { title: 'Delete risk profile?', content: `You are about to delete ${PROFILE_MOCK.name}. Are you sure?`, }, - autoFocus: true, + autoFocus: 'dialog', hasBackdrop: true, disableClose: true, - panelClass: 'simple-dialog', }); openSpy.calls.reset(); @@ -236,10 +235,13 @@ describe('RiskAssessmentComponent', () => { }); it('should call store saveProfile when it is new profile', () => { - expect(mockRiskAssessmentStore.saveProfile).toHaveBeenCalledWith({ + const args = mockRiskAssessmentStore.saveProfile.calls.argsFor(0); + // @ts-expect-error config is in object + expect(args[0].profile).toEqual({ name: 'test', questions: [], }); + expect(mockRiskAssessmentStore.saveProfile).toHaveBeenCalled(); }); it('should close the form', () => { @@ -256,15 +258,13 @@ describe('RiskAssessmentComponent', () => { component.saveProfileClicked(NEW_PROFILE_MOCK, PROFILE_MOCK); expect(openSpy).toHaveBeenCalledWith(SimpleDialogComponent, { - ariaLabel: 'Save profile', data: { title: 'Save profile', content: `You are about to save changes in Primary profile. Are you sure?`, }, - autoFocus: true, + autoFocus: 'dialog', hasBackdrop: true, disableClose: true, - panelClass: 'simple-dialog', }); openSpy.calls.reset(); @@ -278,22 +278,20 @@ describe('RiskAssessmentComponent', () => { component.saveProfileClicked(NEW_PROFILE_MOCK_DRAFT, PROFILE_MOCK); expect(openSpy).toHaveBeenCalledWith(SimpleDialogComponent, { - ariaLabel: 'Save draft profile', data: { title: 'Save draft profile', content: `You are about to save changes in Primary profile. Are you sure?`, }, - autoFocus: true, + autoFocus: 'dialog', hasBackdrop: true, disableClose: true, - panelClass: 'simple-dialog', }); openSpy.calls.reset(); })); it('should call store saveProfile', fakeAsync(() => { - spyOn(component.dialog, 'open').and.returnValue({ + const openSpy = spyOn(component.dialog, 'open').and.returnValue({ afterClosed: () => of(true), } as MatDialogRef); @@ -301,9 +299,23 @@ describe('RiskAssessmentComponent', () => { tick(); - expect(mockRiskAssessmentStore.saveProfile).toHaveBeenCalledWith( - NEW_PROFILE_MOCK - ); + const args = mockRiskAssessmentStore.saveProfile.calls.argsFor(0); + // @ts-expect-error config is in object + expect(args[0].profile).toEqual(NEW_PROFILE_MOCK); + expect(mockRiskAssessmentStore.saveProfile).toHaveBeenCalled(); + openSpy.calls.reset(); + })); + + it('should call store saveProfile and should not open save draft profile modal when profile does not have changes', fakeAsync(() => { + const openSpy = spyOn(component.dialog, 'open').and.returnValue({ + afterClosed: () => of(true), + } as MatDialogRef); + + component.saveProfileClicked(PROFILE_MOCK, PROFILE_MOCK); + + expect(openSpy).not.toHaveBeenCalled(); + expect(mockRiskAssessmentStore.saveProfile).toHaveBeenCalled(); + openSpy.calls.reset(); })); it('should close the form', fakeAsync(() => { @@ -337,15 +349,16 @@ describe('RiskAssessmentComponent', () => { }); describe('with selected profile', () => { - beforeEach(() => { + beforeEach(fakeAsync(() => { component.discard(PROFILE_MOCK); - }); + tick(100); + })); - it('should call setFocusOnCreateButton', () => { + it('should call setFocusOnCreateButton', fakeAsync(() => { expect( mockRiskAssessmentStore.setFocusOnSelectedProfile ).toHaveBeenCalled(); - }); + })); it('should update selected profile', () => { expect( diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts index dd3d33d9d..b6eae14e2 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts @@ -18,15 +18,17 @@ import { Component, OnDestroy, OnInit, + ViewContainerRef, } from '@angular/core'; import { RiskAssessmentStore } from './risk-assessment.store'; import { SimpleDialogComponent } from '../../components/simple-dialog/simple-dialog.component'; -import { Subject, takeUntil } from 'rxjs'; +import { Subject, takeUntil, timer } from 'rxjs'; import { MatDialog } from '@angular/material/dialog'; import { LiveAnnouncer } from '@angular/cdk/a11y'; import { Profile, ProfileStatus } from '../../model/profile'; import { Observable } from 'rxjs/internal/Observable'; import { DeviceValidators } from '../devices/components/device-form/device.validators'; +import { SuccessDialogComponent } from './components/success-dialog/success-dialog.component'; @Component({ selector: 'app-risk-assessment', @@ -42,7 +44,8 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { constructor( private store: RiskAssessmentStore, public dialog: MatDialog, - private liveAnnouncer: LiveAnnouncer + private liveAnnouncer: LiveAnnouncer, + public element: ViewContainerRef ) {} ngOnInit() { @@ -94,15 +97,13 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { selectedProfile: Profile | null ): void { const dialogRef = this.dialog.open(SimpleDialogComponent, { - ariaLabel: 'Delete risk profile', data: { title: 'Delete risk profile?', content: `You are about to delete ${profileName}. Are you sure?`, }, - autoFocus: true, + autoFocus: 'dialog', hasBackdrop: true, disableClose: true, - panelClass: 'simple-dialog', }); dialogRef @@ -113,14 +114,18 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { this.store.deleteProfile(profileName); this.closeFormAfterDelete(profileName, selectedProfile); this.setFocus(index); + } else { + this.store.setFocusOnSelectedProfile(); } }); } saveProfileClicked(profile: Profile, selectedProfile: Profile | null): void { + this.liveAnnouncer.clear(); if (!selectedProfile) { - this.saveProfile(profile); - this.store.setFocusOnCreateButton(); + this.saveProfile(profile, this.store.setFocusOnCreateButton); + } else if (this.compareProfiles(profile, selectedProfile)) { + this.saveProfile(profile, this.store.setFocusOnSelectedProfile); } else { this.openSaveDialog( selectedProfile.name, @@ -129,18 +134,64 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { .pipe(takeUntil(this.destroy$)) .subscribe(saveProfile => { if (saveProfile) { - this.saveProfile(profile); - this.store.setFocusOnSelectedProfile(); + this.saveProfile(profile, this.store.setFocusOnSelectedProfile); } }); } } + private compareProfiles(profile1: Profile, profile2: Profile) { + if (profile1.name !== profile2.name) { + return false; + } + if ( + profile1.rename && + (profile1.rename !== profile1.name || profile1.rename !== profile2.name) + ) { + return false; + } + if (profile1.status !== profile2.status) { + return false; + } + + for (const question of profile1.questions) { + const answer1 = question.answer; + const answer2 = profile2.questions?.find( + question2 => question2.question === question.question + )?.answer; + if (answer1 !== undefined && answer2 !== undefined) { + if (typeof question.answer === 'string') { + if (answer1 !== answer2) { + return false; + } + } else { + //the type of answer is array + if (answer1?.length !== answer2?.length) { + return false; + } + if ( + (answer1 as number[]).some( + answer => !(answer2 as number[]).includes(answer) + ) + ) + return false; + } + } else { + return !!answer1 == !!answer2; + } + } + + return true; + } + discard(selectedProfile: Profile | null) { + this.liveAnnouncer.clear(); this.isOpenProfileForm = false; if (selectedProfile) { - this.store.setFocusOnSelectedProfile(); - this.store.updateSelectedProfile(null); + timer(100).subscribe(() => { + this.store.setFocusOnSelectedProfile(); + this.store.updateSelectedProfile(null); + }); } else { this.store.setFocusOnCreateButton(); } @@ -157,8 +208,17 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { } } - private saveProfile(profile: Profile) { - this.store.saveProfile(profile); + private saveProfile(profile: Profile, focusElement: () => void) { + this.store.saveProfile({ + profile, + onSave: (profile: Profile) => { + if (profile.status === ProfileStatus.VALID) { + this.openSuccessDialog(profile, focusElement); + } else { + focusElement(); + } + }, + }); this.isOpenProfileForm = false; } @@ -178,17 +238,35 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { draft: boolean = false ): Observable { const dialogRef = this.dialog.open(SimpleDialogComponent, { - ariaLabel: `Save ${draft ? 'draft profile' : 'profile'}`, data: { title: `Save ${draft ? 'draft profile' : 'profile'}`, content: `You are about to save changes in ${profileName}. Are you sure?`, }, + autoFocus: 'dialog', + hasBackdrop: true, + disableClose: true, + }); + + return dialogRef?.afterClosed(); + } + + private openSuccessDialog(profile: Profile, focusElement: () => void): void { + const dialogRef = this.dialog.open(SuccessDialogComponent, { + ariaLabel: 'Risk Assessment Profile Completed', + data: { + profile, + }, autoFocus: true, hasBackdrop: true, disableClose: true, panelClass: 'simple-dialog', }); - return dialogRef?.afterClosed(); + dialogRef + ?.afterClosed() + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + focusElement(); + }); } } diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts index 3bc123929..5bd264641 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts @@ -29,7 +29,7 @@ import { import { FocusManagerService } from '../../services/focus-manager.service'; import { AppState } from '../../store/state'; import { selectRiskProfiles } from '../../store/selectors'; -import { fetchRiskProfiles, setRiskProfiles } from '../../store/actions'; +import { setRiskProfiles } from '../../store/actions'; describe('RiskAssessmentStore', () => { let riskAssessmentStore: RiskAssessmentStore; @@ -128,33 +128,35 @@ describe('RiskAssessmentStore', () => { const mockFirstItem = document.createElement('section') as HTMLElement; const mockNullEL = window.document.querySelector(`.mock`) as HTMLElement; - it('should set focus to the next profile item when available', () => { + it('should set focus to the next profile item when available', fakeAsync(() => { const mockData = { nextItem: mockNextItem, firstItem: mockFirstItem, }; riskAssessmentStore.setFocus(mockData); + tick(100); expect( mockFocusManagerService.focusFirstElementInContainer ).toHaveBeenCalledWith(mockNextItem); - }); + })); - it('should set focus to the first profile item when available and no next item', () => { + it('should set focus to the first profile item when available and no next item', fakeAsync(() => { const mockData = { nextItem: mockNullEL, firstItem: mockFirstItem, }; riskAssessmentStore.setFocus(mockData); + tick(100); expect( mockFocusManagerService.focusFirstElementInContainer ).toHaveBeenCalledWith(mockFirstItem); - }); + })); - it('should set focus to the first element in the main when no items', () => { + it('should set focus to the first element in the main when no items', fakeAsync(() => { const mockData = { nextItem: mockNullEL, firstItem: mockFirstItem, @@ -164,11 +166,12 @@ describe('RiskAssessmentStore', () => { store.refreshState(); riskAssessmentStore.setFocus(mockData); + tick(100); expect( mockFocusManagerService.focusFirstElementInContainer ).toHaveBeenCalledWith(); - }); + })); }); describe('setFocusOnCreateButton', () => { @@ -230,10 +233,18 @@ describe('RiskAssessmentStore', () => { }); describe('saveProfile', () => { - it('should dispatch fetchRiskProfiles', () => { - riskAssessmentStore.saveProfile(NEW_PROFILE_MOCK); + it('should dispatch setRiskProfiles', () => { + const onSave = jasmine.createSpy('onSave'); + mockService.fetchProfiles.and.returnValue(of([NEW_PROFILE_MOCK])); + riskAssessmentStore.saveProfile({ + profile: NEW_PROFILE_MOCK, + onSave, + }); - expect(store.dispatch).toHaveBeenCalledWith(fetchRiskProfiles()); + expect(store.dispatch).toHaveBeenCalledWith( + setRiskProfiles({ riskProfiles: [NEW_PROFILE_MOCK] }) + ); + expect(onSave).toHaveBeenCalled(); }); }); }); diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts index 93e89b434..40a8c523a 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts @@ -17,14 +17,14 @@ import { Injectable } from '@angular/core'; import { ComponentStore } from '@ngrx/component-store'; import { tap, withLatestFrom } from 'rxjs/operators'; -import { delay, exhaustMap } from 'rxjs'; +import { catchError, delay, EMPTY, exhaustMap, throwError, timer } from 'rxjs'; import { TestRunService } from '../../services/test-run.service'; import { Profile, ProfileFormat } from '../../model/profile'; import { FocusManagerService } from '../../services/focus-manager.service'; import { Store } from '@ngrx/store'; import { AppState } from '../../store/state'; import { selectRiskProfiles } from '../../store/selectors'; -import { fetchRiskProfiles, setRiskProfiles } from '../../store/actions'; +import { setRiskProfiles } from '../../store/actions'; export interface AppComponentState { selectedProfile: Profile | null; @@ -76,13 +76,15 @@ export class RiskAssessmentStore extends ComponentStore { return trigger$.pipe( withLatestFrom(this.profiles$), tap(([{ nextItem, firstItem }, profiles]) => { - if (nextItem) { - this.focusManagerService.focusFirstElementInContainer(nextItem); - } else if (profiles.length > 1) { - this.focusManagerService.focusFirstElementInContainer(firstItem); - } else { - this.focusManagerService.focusFirstElementInContainer(); - } + timer(100).subscribe(() => { + if (nextItem) { + this.focusManagerService.focusFirstElementInContainer(nextItem); + } else if (profiles.length > 1) { + this.focusManagerService.focusFirstElementInContainer(firstItem); + } else { + this.focusManagerService.focusFirstElementInContainer(); + } + }); }) ); } @@ -131,14 +133,30 @@ export class RiskAssessmentStore extends ComponentStore { ); }); - saveProfile = this.effect(trigger$ => { + saveProfile = this.effect<{ + profile: Profile; + onSave: ((profile: Profile) => void) | undefined; + }>(trigger$ => { return trigger$.pipe( - exhaustMap((name: Profile) => { - return this.testRunService.saveProfile(name).pipe( - tap(saved => { + exhaustMap(({ profile, onSave }) => { + return this.testRunService.saveProfile(profile).pipe( + exhaustMap(saved => { if (saved) { - this.store.dispatch(fetchRiskProfiles()); + return this.testRunService.fetchProfiles(); + } + return throwError('Failed to upload profile'); + }), + tap(newProfiles => { + this.store.dispatch(setRiskProfiles({ riskProfiles: newProfiles })); + const uploadedProfile = newProfiles.find( + p => p.name === profile.name || p.name === profile.rename + ); + if (onSave && uploadedProfile) { + onSave(uploadedProfile); } + }), + catchError(() => { + return EMPTY; }) ); }) diff --git a/modules/ui/src/app/pages/settings/settings.component.scss b/modules/ui/src/app/pages/settings/settings.component.scss index 770d79a52..6596a3591 100644 --- a/modules/ui/src/app/pages/settings/settings.component.scss +++ b/modules/ui/src/app/pages/settings/settings.component.scss @@ -146,3 +146,10 @@ } } } + +.settings-drawer-header-button:not(.mat-mdc-button-disabled), +.close-button:not(.mat-mdc-button-disabled), +.save-button:not(.mat-mdc-button-disabled) { + cursor: pointer; + pointer-events: auto; +} diff --git a/modules/ui/src/app/pages/settings/settings.component.ts b/modules/ui/src/app/pages/settings/settings.component.ts index 1f9b4f62a..b4fb17c9e 100644 --- a/modules/ui/src/app/pages/settings/settings.component.ts +++ b/modules/ui/src/app/pages/settings/settings.component.ts @@ -52,7 +52,11 @@ export class SettingsComponent implements OnInit, OnDestroy { } @Input() set settingsDisable(value: boolean) { this.isSettingsDisable = value; - value ? this.disableSettings() : this.enableSettings(); + if (value) { + this.disableSettings(); + } else { + this.enableSettings(); + } } public readonly CalloutType = CalloutType; public readonly EventType = EventType; diff --git a/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.scss b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.scss index cf368929d..a08461b6d 100644 --- a/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.scss +++ b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.scss @@ -22,7 +22,7 @@ $option-height: 36px; .download-options-field { width: $option-width; - background: mat.get-color-from-palette($color-primary, 50); + background: mat.m2-get-color-from-palette($color-primary, 50); ::ng-deep.mat-mdc-text-field-wrapper { padding: 0 16px; @@ -39,7 +39,7 @@ $option-height: 36px; } ::ng-deep.mat-mdc-select-placeholder { - color: mat.get-color-from-palette($color-primary, 700); + color: mat.m2-get-color-from-palette($color-primary, 700); font-size: 14px; font-style: normal; font-weight: 500; @@ -48,11 +48,11 @@ $option-height: 36px; } ::ng-deep.mat-mdc-select-arrow { - color: mat.get-color-from-palette($color-primary, 700); + color: mat.m2-get-color-from-palette($color-primary, 700); } ::ng-deep .mat-mdc-text-field-wrapper .mdc-notched-outline > * { - border-color: mat.get-color-from-palette($color-primary, 50); + border-color: mat.m2-get-color-from-palette($color-primary, 50); } ::ng-deep diff --git a/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.ts b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.ts index fddd6822f..81b4a1fee 100644 --- a/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.ts +++ b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.ts @@ -97,6 +97,9 @@ export class DownloadOptionsComponent { } getReportTitle(data: TestrunStatus) { + if (!data.device) { + return ''; + } return `${data.device.manufacturer} ${data.device.model} ${ data.device.firmware } ${data.status} ${this.getFormattedDateString(data.started)}` diff --git a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.html b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.html index c576d242e..6caab0423 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.html +++ b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.html @@ -22,6 +22,7 @@ { component.startTestRun(); expect(testRunServiceMock.startTestrun).toHaveBeenCalledWith({ + status: DeviceStatus.VALID, manufacturer: 'Delta', model: 'O3-DIN-CPU', mac_addr: '00:1e:42:35:73:c4', @@ -230,7 +236,7 @@ describe('ProgressInitiateFormComponent', () => { expect(buttonSpy).toHaveBeenCalled(); }); - it('should focus firmware', () => { + it('should focus firmware', fakeAsync(() => { component.selectedDevice = device; component.setFirmwareFocus = true; const firmwareSpy = spyOn( @@ -239,9 +245,10 @@ describe('ProgressInitiateFormComponent', () => { ); component.ngAfterViewChecked(); fixture.detectChanges(); + tick(100); expect(firmwareSpy).toHaveBeenCalled(); - }); + })); }); it('should focus element on focusButton ', () => { @@ -303,12 +310,6 @@ describe('ProgressInitiateFormComponent', () => { expect(deviceItem).toBeTruthy(); }); - it('should have tabindex -1 for device item', () => { - const deviceItem = compiled.querySelector('app-device-item button'); - - expect((deviceItem as HTMLElement).tabIndex).toBe(-1); - }); - it('should display firmware if device selected', () => { const firmware = compiled.querySelector('input'); diff --git a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts index a526e0973..cbec35a0a 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts +++ b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts @@ -24,7 +24,12 @@ import { } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { TestRunService } from '../../../../services/test-run.service'; -import { Device, TestModule, DeviceView } from '../../../../model/device'; +import { + Device, + TestModule, + DeviceStatus, + DeviceView, +} from '../../../../model/device'; import { AbstractControl, FormArray, @@ -33,7 +38,7 @@ import { } from '@angular/forms'; import { DeviceValidators } from '../../../devices/components/device-form/device.validators'; import { EscapableDialogComponent } from '../../../../components/escapable-dialog/escapable-dialog.component'; -import { take } from 'rxjs'; +import { take, timer } from 'rxjs'; import { Store } from '@ngrx/store'; import { AppState } from '../../../../store/state'; import { selectDevices } from '../../../../store/selectors'; @@ -61,6 +66,7 @@ export class TestrunInitiateFormComponent testModules: TestModule[] = []; prevDevice: Device | null = null; setFirmwareFocus = false; + readonly DeviceStatus = DeviceStatus; readonly DeviceView = DeviceView; error$: BehaviorSubject = new BehaviorSubject( null @@ -121,9 +127,11 @@ export class TestrunInitiateFormComponent this.changeDetectorRef.detectChanges(); } if (this.setFirmwareFocus) { - this.firmwareInput?.nativeElement.focus(); - this.setFirmwareFocus = false; - this.changeDetectorRef.detectChanges(); + timer(100).subscribe(() => { + this.firmwareInput?.nativeElement.focus(); + this.setFirmwareFocus = false; + this.changeDetectorRef.detectChanges(); + }); } } diff --git a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html index b94c0d55a..be4d3b259 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html +++ b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html @@ -40,7 +40,7 @@ *ngTemplateOutlet=" InProgress; context: { - data: data + data: data, } "> @@ -53,7 +53,7 @@ *ngTemplateOutlet=" Finished; context: { - data: data + data: data, } "> @@ -64,7 +64,7 @@ *ngTemplateOutlet=" Finished; context: { - data: data + data: data, } "> @@ -73,7 +73,7 @@ *ngTemplateOutlet=" InProgress; context: { - data: data + data: data, } "> diff --git a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.scss b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.scss index ddf8a41b8..b7c86a91a 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.scss +++ b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.scss @@ -33,11 +33,11 @@ padding: 16px 32px; &.progress { - background-color: mat.get-color-from-palette($color-primary, 700); + background-color: mat.m2-get-color-from-palette($color-primary, 700); } &.completed-success { - background-color: mat.get-color-from-palette($color-accent, 700); + background-color: mat.m2-get-color-from-palette($color-accent, 700); } &.completed-failed { diff --git a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.spec.ts b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.spec.ts index 29fdd7731..2e35d0581 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.spec.ts +++ b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.spec.ts @@ -26,6 +26,7 @@ import { MOCK_PROGRESS_DATA_IN_PROGRESS, MOCK_PROGRESS_DATA_MONITORING, MOCK_PROGRESS_DATA_WAITING_FOR_DEVICE, + MOCK_PROGRESS_DATA_WITH_ERROR, } from '../../../../mocks/testrun.mock'; import { TestrunModule } from '../../testrun.module'; @@ -129,7 +130,7 @@ describe('ProgressStatusCardComponent', () => { }); it('should return correct test result if status "Compliant"', () => { - const expectedResult = '2/2'; + const expectedResult = '2/3'; const result = component.getTestsResult(MOCK_PROGRESS_DATA_COMPLIANT); @@ -144,6 +145,14 @@ describe('ProgressStatusCardComponent', () => { expect(result).toEqual(expectedResult); }); + it('should not include Error and Not Started status in completed test result', () => { + const expectedResult = '1/3'; + + const result = component.getTestsResult(MOCK_PROGRESS_DATA_WITH_ERROR); + + expect(result).toEqual(expectedResult); + }); + it('should return empty string if no data', () => { const expectedResult = ''; diff --git a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.ts b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.ts index 027588b24..d84b692e1 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.ts +++ b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.ts @@ -16,6 +16,7 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { IResult, + StatusOfTestResult, StatusOfTestrun, TestrunStatus, TestsData, @@ -65,11 +66,11 @@ export class TestrunStatusCardComponent { (data.tests as TestsData)?.results?.length && (data.tests as TestsData)?.total ) { - return `${(data.tests as TestsData)?.results?.length}/${ + return `${(data.tests as TestsData)?.results?.filter(result => result.result !== StatusOfTestResult.NotStarted && result.result !== StatusOfTestResult.Error).length}/${ (data.tests as TestsData)?.total }`; } else if ((data.tests as IResult[])?.length) { - return `${(data.tests as IResult[])?.length}/${ + return `${(data.tests as IResult[])?.filter(result => result.result !== StatusOfTestResult.NotStarted && result.result !== StatusOfTestResult.Error).length}/${ (data.tests as IResult[])?.length }`; } @@ -99,7 +100,13 @@ export class TestrunStatusCardComponent { const testData = data.tests as TestsData; if (testData && testData.total && testData.results?.length) { - return Math.round((testData.results.length / testData.total) * 100); + return Math.round( + (testData.results.filter( + result => result.result !== StatusOfTestResult.NotStarted + ).length / + testData.total) * + 100 + ); } return 0; } diff --git a/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.spec.ts b/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.spec.ts index 60c325c3d..0632ff75c 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.spec.ts +++ b/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.spec.ts @@ -55,6 +55,7 @@ describe('ProgressTableComponent', () => { green: false, red: true, blue: false, + cyan: false, grey: false, }; diff --git a/modules/ui/src/app/pages/testrun/testrun.component.html b/modules/ui/src/app/pages/testrun/testrun.component.html index 74b414abf..042a7815f 100644 --- a/modules/ui/src/app/pages/testrun/testrun.component.html +++ b/modules/ui/src/app/pages/testrun/testrun.component.html @@ -30,16 +30,29 @@ startNewTestrunButton; context: { hasDevices: vm.hasDevices, - systemStatus: vm.systemStatus + systemStatus: vm.systemStatus, + isAllDevicesOutdated: vm.isAllDevicesOutdated, } ">

- {{ data.device.manufacturer }} {{ data.device.model }} v{{ - data.device.firmware - }} + {{ getTestRunName(data) }}

+ + {{ tag }} +
@@ -89,6 +103,7 @@

{ let router: Router; let mockService: SpyObj; let store: MockStore; - let focusNavigation = true; let mockFocusManagerService: SpyObj; let mockLiveAnnouncer: SpyObj; let mockMqttService: SpyObj; @@ -156,23 +149,14 @@ describe('AppComponent', () => { { provide: TestRunMqttService, useValue: mockMqttService }, { provide: State, - useValue: { - getValue: () => ({ - [appFeatureKey]: { - appComponent: { - focusNavigation: focusNavigation, - }, - }, - }), - }, + useValue: {}, }, provideMockStore({ selectors: [ { selector: selectInterfaces, value: {} }, { selector: selectHasConnectionSettings, value: true }, { selector: selectInternetConnection, value: true }, - { selector: selectError, value: null }, - { selector: selectMenuOpened, value: false }, + { selector: selectSystemConfig, value: { network: {} } }, { selector: selectHasDevices, value: false }, { selector: selectIsAllDevicesOutdated, value: false }, { selector: selectHasExpiredDevices, value: false }, @@ -206,6 +190,7 @@ describe('AppComponent', () => { router = TestBed.get(Router); compiled = fixture.nativeElement as HTMLElement; spyOn(store, 'dispatch').and.callFake(() => {}); + component.appStore.updateSettingMissedError(null); }); it('should create the app', () => { @@ -397,17 +382,21 @@ describe('AppComponent', () => { }); it('should dispatch toggleMenu action', () => { + spyOn(component.appStore, 'toggleMenu'); + const menuBtn = compiled.querySelector( '.app-toolbar-button-menu' ) as HTMLButtonElement; menuBtn.click(); - expect(store.dispatch).toHaveBeenCalledWith(toggleMenu()); + expect(component.appStore.toggleMenu).toHaveBeenCalled(); }); it('should focus navigation on tab press if menu button was clicked', () => { - focusNavigation = true; + component.appStore.updateFocusNavigation(true); + fixture.detectChanges(); + spyOn(component.appStore, 'updateFocusNavigation'); const menuBtn = compiled.querySelector( '.app-toolbar-button-menu' ) as HTMLButtonElement; @@ -415,8 +404,8 @@ describe('AppComponent', () => { menuBtn.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' })); const navigation = compiled.querySelector('.app-sidebar'); - expect(store.dispatch).toHaveBeenCalledWith( - updateFocusNavigation({ focusNavigation: false }) + expect(component.appStore.updateFocusNavigation).toHaveBeenCalledWith( + false ); expect( mockFocusManagerService.focusFirstElementInContainer @@ -424,7 +413,8 @@ describe('AppComponent', () => { }); it('should not focus navigation button on tab press if menu button was not clicked', () => { - focusNavigation = false; + component.appStore.updateFocusNavigation(false); + fixture.detectChanges(); const menuBtn = compiled.querySelector( '.app-toolbar-button-menu' ) as HTMLButtonElement; @@ -712,7 +702,7 @@ describe('AppComponent', () => { }); }); - describe('with devices setted, without systemStatus data, but run the tests ', () => { + describe('with devices setted, without systemStatus data, but run the tests', () => { beforeEach(() => { store.overrideSelector(selectHasDevices, true); fixture.detectChanges(); @@ -745,7 +735,7 @@ describe('AppComponent', () => { describe('error', () => { describe('with settingMissedError with one port is missed', () => { beforeEach(() => { - store.overrideSelector(selectError, { + component.appStore.updateSettingMissedError({ isSettingMissed: true, devicePortMissed: true, internetPortMissed: false, @@ -764,7 +754,7 @@ describe('AppComponent', () => { describe('with settingMissedError with two ports are missed', () => { beforeEach(() => { - store.overrideSelector(selectError, { + component.appStore.updateSettingMissedError({ isSettingMissed: true, devicePortMissed: true, internetPortMissed: true, @@ -783,7 +773,7 @@ describe('AppComponent', () => { describe('with no settingMissedError', () => { beforeEach(() => { - store.overrideSelector(selectError, null); + component.appStore.updateSettingMissedError(null); store.overrideSelector(selectHasDevices, true); fixture.detectChanges(); }); diff --git a/modules/ui/src/app/app.component.ts b/modules/ui/src/app/app.component.ts index 7218459c4..ba8d33831 100644 --- a/modules/ui/src/app/app.component.ts +++ b/modules/ui/src/app/app.component.ts @@ -31,12 +31,7 @@ import { Routes } from './model/routes'; import { FocusManagerService } from './services/focus-manager.service'; import { State, Store } from '@ngrx/store'; import { AppState } from './store/state'; -import { - setIsOpenAddDevice, - toggleMenu, - updateFocusNavigation, -} from './store/actions'; -import { appFeatureKey } from './store/reducers'; +import { setIsOpenAddDevice } from './store/actions'; import { SettingsComponent } from './pages/settings/settings.component'; import { AppStore } from './app.store'; import { TestRunService } from './services/test-run.service'; @@ -205,19 +200,19 @@ export class AppComponent implements AfterViewInit { public toggleMenu(event: MouseEvent) { event.stopPropagation(); - this.store.dispatch(toggleMenu()); + this.appStore.toggleMenu(); } /** * When side menu is opened */ - skipToNavigation(event: Event) { - if (this.state.getValue()[appFeatureKey].appComponent.focusNavigation) { + skipToNavigation(event: Event, focusNavigation: boolean) { + if (focusNavigation) { event.preventDefault(); // if not prevented, second element will be focused this.focusManagerService.focusFirstElementInContainer( this.navigation.nativeElement ); - this.store.dispatch(updateFocusNavigation({ focusNavigation: false })); // user will be navigated according to normal flow on tab + this.appStore.updateFocusNavigation(false); // user will be navigated according to normal flow on tab } } diff --git a/modules/ui/src/app/app.store.spec.ts b/modules/ui/src/app/app.store.spec.ts index 4236b24fd..300a250fd 100644 --- a/modules/ui/src/app/app.store.spec.ts +++ b/modules/ui/src/app/app.store.spec.ts @@ -19,7 +19,6 @@ import { AppStore, CALLOUT_STATE_KEY, CONSENT_SHOWN_KEY } from './app.store'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { AppState } from './store/state'; import { - selectError, selectHasConnectionSettings, selectHasDevices, selectHasExpiredDevices, @@ -29,10 +28,10 @@ import { selectIsAllDevicesOutdated, selectIsOpenWaitSnackBar, selectIsTestingComplete, - selectMenuOpened, selectReports, selectRiskProfiles, selectStatus, + selectSystemConfig, selectSystemStatus, selectTestModules, } from './store/selectors'; @@ -53,6 +52,7 @@ import { NotificationService } from './services/notification.service'; import { FocusManagerService } from './services/focus-manager.service'; import { TestRunMqttService } from './services/test-run-mqtt.service'; import { MOCK_ADAPTERS } from './mocks/settings.mock'; +import { TestingType } from './model/device'; const mock = (() => { let store: { [key: string]: string } = {}; @@ -87,6 +87,8 @@ describe('AppStore', () => { let mockMqttService: SpyObj; beforeEach(() => { + window.sessionStorage.clear(); + mockService = jasmine.createSpyObj('mockService', [ 'fetchDevices', 'getTestModules', @@ -112,6 +114,8 @@ describe('AppStore', () => { { selector: selectSystemStatus, value: null }, { selector: selectIsTestingComplete, value: false }, { selector: selectRiskProfiles, value: [] }, + { selector: selectSystemConfig, value: { network: {} } }, + { selector: selectInterfaces, value: {} }, ], }), { provide: TestRunService, useValue: mockService }, @@ -130,9 +134,7 @@ describe('AppStore', () => { store.overrideSelector(selectHasRiskProfiles, false); store.overrideSelector(selectReports, []); store.overrideSelector(selectHasConnectionSettings, true); - store.overrideSelector(selectMenuOpened, true); store.overrideSelector(selectInterfaces, {}); - store.overrideSelector(selectError, null); store.overrideSelector(selectStatus, null); spyOn(store, 'dispatch').and.callFake(() => {}); @@ -182,8 +184,9 @@ describe('AppStore', () => { isTestingComplete: false, riskProfiles: [], hasConnectionSettings: true, - isMenuOpen: true, + isMenuOpen: false, interfaces: {}, + focusNavigation: false, settingMissedError: null, calloutState: new Map(), hasInternetConnection: false, @@ -345,5 +348,177 @@ describe('AppStore', () => { expect(mock.getObject(CALLOUT_STATE_KEY)).toBeTruthy(); }); }); + + describe('checkInterfacesInConfig', () => { + it('should update settingMissedError with all false if all ports are present', done => { + appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { + expect(store.settingMissedError).toEqual({ + isSettingMissed: false, + devicePortMissed: false, + internetPortMissed: false, + }); + done(); + }); + + store.overrideSelector(selectInterfaces, { + enx00e04c020fa8: '00:e0:4c:02:0f:a8', + enx207bd26205e9: '20:7b:d2:62:05:e9', + }); + store.overrideSelector(selectSystemConfig, { + network: { + device_intf: 'enx00e04c020fa8', + internet_intf: 'enx207bd26205e9', + }, + }); + store.refreshState(); + }); + + it('should update settingMissedError with all true if all ports are missing', done => { + appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { + expect(store.settingMissedError).toEqual({ + isSettingMissed: true, + devicePortMissed: true, + internetPortMissed: true, + }); + done(); + }); + + store.overrideSelector(selectInterfaces, { + enx00e04c020fa9: '00:e0:4c:02:0f:a8', + enx207bd26205e8: '20:7b:d2:62:05:e9', + }); + store.overrideSelector(selectSystemConfig, { + network: { + device_intf: 'enx00e04c020fa8', + internet_intf: 'enx207bd26205e9', + }, + }); + store.refreshState(); + }); + + it('should update settingMissedError with devicePortMissed true if device port is missing', done => { + appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { + expect(store.settingMissedError).toEqual({ + isSettingMissed: true, + devicePortMissed: true, + internetPortMissed: false, + }); + done(); + }); + + store.overrideSelector(selectInterfaces, { + enx00e04c020fa9: '00:e0:4c:02:0f:a8', + enx207bd26205e8: '20:7b:d2:62:05:e9', + }); + store.overrideSelector(selectSystemConfig, { + network: { + device_intf: 'enx00e04c020fa8', + internet_intf: 'enx207bd26205e8', + }, + }); + store.refreshState(); + }); + + it('should update settingMissedError with internetPortMissed true if device internet is missing', done => { + appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { + expect(store.settingMissedError).toEqual({ + isSettingMissed: true, + devicePortMissed: false, + internetPortMissed: true, + }); + done(); + }); + + store.overrideSelector(selectInterfaces, { + enx00e04c020fa9: '00:e0:4c:02:0f:a8', + enx207bd26205e8: '20:7b:d2:62:05:e9', + }); + store.overrideSelector(selectSystemConfig, { + network: { + device_intf: 'enx00e04c020fa9', + internet_intf: 'enx207bd26205e9', + }, + }); + store.refreshState(); + }); + + it('should update settingMissedError with all false if interface are not empty and config is not set', done => { + appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { + expect(store.settingMissedError).toEqual({ + isSettingMissed: false, + devicePortMissed: false, + internetPortMissed: false, + }); + done(); + }); + + store.overrideSelector(selectInterfaces, { + enx00e04c020fa8: '00:e0:4c:02:0f:a8', + enx207bd26205e9: '20:7b:d2:62:05:e9', + }); + store.overrideSelector(selectSystemConfig, { + network: { + device_intf: '', + internet_intf: '', + }, + }); + store.refreshState(); + }); + + it('should update settingMissedError with all false if interface are empty and config is not set', done => { + appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { + expect(store.settingMissedError).toEqual({ + isSettingMissed: false, + devicePortMissed: false, + internetPortMissed: false, + }); + done(); + }); + + store.overrideSelector(selectInterfaces, {}); + store.overrideSelector(selectSystemConfig, { + network: { + device_intf: '', + internet_intf: '', + }, + }); + store.refreshState(); + }); + + it('should send GA event', done => { + // @ts-expect-error data layer should be defined + window.dataLayer = window.dataLayer || []; + + appStore.viewModel$.pipe(skip(1), take(1)).subscribe(() => { + expect( + // @ts-expect-error data layer should be defined + window.dataLayer.some(event => event.event === 'pilot_is_compliant') + ).toBeTrue(); + done(); + }); + + store.overrideSelector(selectIsTestingComplete, true); + store.overrideSelector(selectSystemStatus, { + status: 'Compliant', + mac_addr: '00:1e:42:35:73:c4', + device: { + manufacturer: 'Delta', + model: '03-DIN-CPU', + mac_addr: '00:1e:42:35:73:c4', + firmware: '1.2.2', + test_pack: TestingType.Pilot, + }, + started: '2023-06-22T09:20:00.123Z', + finished: '2023-06-22T09:26:00.123Z', + report: 'https://api.testrun.io/report.pdf', + tags: [], + tests: { + total: 3, + results: [], + }, + }); + store.refreshState(); + }); + }); }); }); diff --git a/modules/ui/src/app/app.store.ts b/modules/ui/src/app/app.store.ts index 91280724c..a12a536a3 100644 --- a/modules/ui/src/app/app.store.ts +++ b/modules/ui/src/app/app.store.ts @@ -16,9 +16,8 @@ import { Injectable } from '@angular/core'; import { ComponentStore } from '@ngrx/component-store'; -import { tap } from 'rxjs/operators'; +import { tap, withLatestFrom } from 'rxjs/operators'; import { - selectError, selectHasConnectionSettings, selectHasDevices, selectHasExpiredDevices, @@ -27,17 +26,24 @@ import { selectInternetConnection, selectIsAllDevicesOutdated, selectIsTestingComplete, - selectMenuOpened, selectReports, selectRiskProfiles, selectStatus, + selectSystemConfig, selectSystemStatus, } from './store/selectors'; import { Store } from '@ngrx/store'; import { AppState } from './store/state'; import { TestRunService } from './services/test-run.service'; -import { delay, exhaustMap, Observable, skip } from 'rxjs'; -import { Device, TestModule } from './model/device'; +import { + combineLatest, + delay, + exhaustMap, + filter, + Observable, + skip, +} from 'rxjs'; +import { Device, TestingType, TestModule } from './model/device'; import { setDevices, setIsOpenStartTestrun, @@ -47,10 +53,11 @@ import { setTestModules, updateAdapters, } from './store/actions'; -import { TestrunStatus } from './model/testrun-status'; +import { StatusOfTestrun, TestrunStatus } from './model/testrun-status'; import { Adapters, SettingMissedError, + SystemConfig, SystemInterfaces, } from './model/setting'; import { FocusManagerService } from './services/focus-manager.service'; @@ -65,6 +72,12 @@ export interface AppComponentState { isStatusLoaded: boolean; systemStatus: TestrunStatus | null; calloutState: Map; + isMenuOpen: boolean; + /** + * Indicates, if side menu should be focused on keyboard navigation after menu is opened + */ + focusNavigation: boolean; + settingMissedError: SettingMissedError | null; } @Injectable() export class AppStore extends ComponentStore { @@ -80,11 +93,14 @@ export class AppStore extends ComponentStore { private hasConnectionSetting$ = this.store.select( selectHasConnectionSettings ); - private isMenuOpen$ = this.store.select(selectMenuOpened); + private isMenuOpened$ = this.select(state => state.isMenuOpen); + private focusNavigation$ = this.select(state => state.focusNavigation); private interfaces$: Observable = this.store.select(selectInterfaces); + private systemConfig$: Observable = + this.store.select(selectSystemConfig); private settingMissedError$: Observable = - this.store.select(selectError); + this.select(state => state.settingMissedError); systemStatus$: Observable = this.store.select(selectStatus); testrunStatus$: Observable = this.store.select(selectSystemStatus); @@ -106,11 +122,12 @@ export class AppStore extends ComponentStore { isTestingComplete: this.isTestingComplete$, riskProfiles: this.riskProfiles$, hasConnectionSettings: this.hasConnectionSetting$, - isMenuOpen: this.isMenuOpen$, + isMenuOpen: this.isMenuOpened$, interfaces: this.interfaces$, settingMissedError: this.settingMissedError$, calloutState: this.calloutState$, hasInternetConnection: this.hasInternetConnection$, + focusNavigation: this.focusNavigation$, }); updateConsent = this.updater((state, consentShown: boolean) => ({ @@ -134,6 +151,23 @@ export class AppStore extends ComponentStore { isStatusLoaded, })); + updateFocusNavigation = this.updater((state, focusNavigation: boolean) => ({ + ...state, + focusNavigation, + })); + + updateIsMenuOpened = this.updater((state, isMenuOpen: boolean) => ({ + ...state, + isMenuOpen, + })); + + updateSettingMissedError = this.updater( + (state, settingMissedError: SettingMissedError | null) => ({ + ...state, + settingMissedError, + }) + ); + setContent = this.effect(trigger$ => { return trigger$.pipe( tap(() => { @@ -259,6 +293,58 @@ export class AppStore extends ComponentStore { ); }); + toggleMenu = this.effect(trigger$ => { + return trigger$.pipe( + withLatestFrom(this.isMenuOpened$), + tap(([, opened]) => { + this.updateIsMenuOpened(!opened); + if (!opened) { + this.updateFocusNavigation(true); + } + }) + ); + }); + + checkInterfacesInConfig = this.effect(() => { + return combineLatest([ + this.interfaces$.pipe(skip(1)), + this.systemConfig$.pipe(skip(1)), + ]).pipe( + filter(([, { network }]) => network !== null), + tap(([interfaces, { network, single_intf }]) => { + const deviceValid = + network?.device_intf == '' || + (!!network?.device_intf && !!interfaces[network.device_intf]); + const internetValid = single_intf + ? true + : network?.internet_intf == '' || + (!!network?.internet_intf && !!interfaces[network.internet_intf]); + this.updateSettingMissedError({ + isSettingMissed: !deviceValid || !internetValid, + devicePortMissed: !deviceValid, + internetPortMissed: !internetValid, + }); + }) + ); + }); + + sendGAEvent = this.effect(() => { + return combineLatest([this.isTestingComplete$, this.testrunStatus$]).pipe( + filter(([isTestingComplete]) => isTestingComplete === true), + filter( + ([, testrunStatus]) => + testrunStatus?.status === StatusOfTestrun.Compliant && + testrunStatus?.device.test_pack === TestingType.Pilot + ), + tap(() => { + // @ts-expect-error data layer is not null + window.dataLayer.push({ + event: 'pilot_is_compliant', + }); + }) + ); + }); + constructor( private store: Store, private testRunService: TestRunService, @@ -276,6 +362,9 @@ export class AppStore extends ComponentStore { calloutState: calloutState ? new Map(Object.entries(calloutState)) : new Map(), + isMenuOpen: false, + focusNavigation: false, + settingMissedError: null, }); } } diff --git a/modules/ui/src/app/components/download-report-zip/download-report-zip.component.spec.ts b/modules/ui/src/app/components/download-report-zip/download-report-zip.component.spec.ts index 1f0919286..c0a2bd18b 100644 --- a/modules/ui/src/app/components/download-report-zip/download-report-zip.component.spec.ts +++ b/modules/ui/src/app/components/download-report-zip/download-report-zip.component.spec.ts @@ -13,36 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - ComponentFixture, - discardPeriodicTasks, - fakeAsync, - TestBed, - tick, -} from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing'; import { DownloadReportZipComponent } from './download-report-zip.component'; import { of } from 'rxjs'; import { MatDialogRef } from '@angular/material/dialog'; -import { DownloadZipModalComponent } from '../download-zip-modal/download-zip-modal.component'; -import { Router } from '@angular/router'; -import { TestRunService } from '../../services/test-run.service'; -import { Routes } from '../../model/routes'; +import { + DialogCloseAction, + DownloadZipModalComponent, +} from '../download-zip-modal/download-zip-modal.component'; import { RouterTestingModule } from '@angular/router/testing'; import { Component } from '@angular/core'; import { MOCK_PROGRESS_DATA_COMPLIANT } from '../../mocks/testrun.mock'; -import { FocusManagerService } from '../../services/focus-manager.service'; describe('DownloadReportZipComponent', () => { let component: DownloadReportZipComponent; let fixture: ComponentFixture; let compiled: HTMLElement; - let router: Router; - - const testrunServiceMock: jasmine.SpyObj = - jasmine.createSpyObj('testrunServiceMock', ['downloadZip']); - const focusServiceMock: jasmine.SpyObj = - jasmine.createSpyObj('focusServiceMock', ['focusFirstElementInContainer']); beforeEach(async () => { await TestBed.configureTestingModule({ @@ -52,13 +39,8 @@ describe('DownloadReportZipComponent', () => { ]), DownloadReportZipComponent, ], - providers: [ - { provide: TestRunService, useValue: testrunServiceMock }, - { provide: FocusManagerService, useValue: focusServiceMock }, - ], }).compileComponents(); fixture = TestBed.createComponent(DownloadReportZipComponent); - router = TestBed.get(Router); compiled = fixture.nativeElement as HTMLElement; component = fixture.componentInstance; component.url = 'localhost:8080'; @@ -71,87 +53,25 @@ describe('DownloadReportZipComponent', () => { }); describe('#onClick', () => { - beforeEach(() => { - testrunServiceMock.downloadZip.calls.reset(); - }); - - it('should call service if profile is a string', fakeAsync(() => { + it('should open zip modal dialog', fakeAsync(() => { const openSpy = spyOn(component.dialog, 'open').and.returnValue({ - afterClosed: () => of(''), + afterClosed: () => + of({ action: DialogCloseAction.Download, profile: '' }), } as MatDialogRef); - component.onClick(new Event('click')); expect(openSpy).toHaveBeenCalledWith(DownloadZipModalComponent, { ariaLabel: 'Download zip', data: { profiles: [], + url: 'localhost:8080', + isPilot: false, }, autoFocus: true, hasBackdrop: true, disableClose: true, panelClass: 'initiate-test-run-dialog', }); - - tick(); - - expect(testrunServiceMock.downloadZip).toHaveBeenCalled(); - expect(router.url).not.toBe(Routes.RiskAssessment); - openSpy.calls.reset(); - })); - - it('should navigate to risk profiles page if profile is null', fakeAsync(() => { - const openSpy = spyOn(component.dialog, 'open').and.returnValue({ - afterClosed: () => of(null), - } as MatDialogRef); - - fixture.ngZone?.run(() => { - component.onClick(new Event('click')); - - expect(openSpy).toHaveBeenCalledWith(DownloadZipModalComponent, { - ariaLabel: 'Download zip', - data: { - profiles: [], - }, - autoFocus: true, - hasBackdrop: true, - disableClose: true, - panelClass: 'initiate-test-run-dialog', - }); - - tick(100); - - expect(router.url).toBe(Routes.RiskAssessment); - expect( - focusServiceMock.focusFirstElementInContainer - ).toHaveBeenCalled(); - - openSpy.calls.reset(); - discardPeriodicTasks(); - }); - })); - - it('should not call service to download zip if profile is undefined', fakeAsync(() => { - const openSpy = spyOn(component.dialog, 'open').and.returnValue({ - afterClosed: () => of(undefined), - } as MatDialogRef); - - component.onClick(new Event('click')); - - expect(openSpy).toHaveBeenCalledWith(DownloadZipModalComponent, { - ariaLabel: 'Download zip', - data: { - profiles: [], - }, - autoFocus: true, - hasBackdrop: true, - disableClose: true, - panelClass: 'initiate-test-run-dialog', - }); - - tick(); - - expect(testrunServiceMock.downloadZip).not.toHaveBeenCalled(); openSpy.calls.reset(); })); }); diff --git a/modules/ui/src/app/components/download-report-zip/download-report-zip.component.ts b/modules/ui/src/app/components/download-report-zip/download-report-zip.component.ts index c643cc687..742210937 100644 --- a/modules/ui/src/app/components/download-report-zip/download-report-zip.component.ts +++ b/modules/ui/src/app/components/download-report-zip/download-report-zip.component.ts @@ -19,20 +19,15 @@ import { HostBinding, HostListener, Input, - OnDestroy, OnInit, } from '@angular/core'; import { CommonModule, DatePipe } from '@angular/common'; import { Profile } from '../../model/profile'; import { MatDialog } from '@angular/material/dialog'; -import { Subject, takeUntil, timer } from 'rxjs'; -import { Routes } from '../../model/routes'; import { DownloadZipModalComponent } from '../download-zip-modal/download-zip-modal.component'; -import { TestRunService } from '../../services/test-run.service'; -import { Router } from '@angular/router'; import { ReportActionComponent } from '../report-action/report-action.component'; import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip'; -import { FocusManagerService } from '../../services/focus-manager.service'; +import { TestingType } from '../../model/device'; @Component({ selector: 'app-download-report-zip', @@ -45,9 +40,8 @@ import { FocusManagerService } from '../../services/focus-manager.service'; }) export class DownloadReportZipComponent extends ReportActionComponent - implements OnDestroy, OnInit + implements OnInit { - private destroy$: Subject = new Subject(); @Input() profiles: Profile[] = []; @Input() url: string | null | undefined = null; @@ -58,36 +52,18 @@ export class DownloadReportZipComponent event.preventDefault(); event.stopPropagation(); - const dialogRef = this.dialog.open(DownloadZipModalComponent, { + this.dialog.open(DownloadZipModalComponent, { ariaLabel: 'Download zip', data: { profiles: this.profiles, + url: this.url, + isPilot: this.data?.device.test_pack === TestingType.Pilot, }, autoFocus: true, hasBackdrop: true, disableClose: true, panelClass: 'initiate-test-run-dialog', }); - - dialogRef - ?.afterClosed() - .pipe(takeUntil(this.destroy$)) - .subscribe(profile => { - if (profile === undefined) { - return; - } - if (profile === null) { - this.route.navigate([Routes.RiskAssessment]).then(() => - timer(100) - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - this.focusManagerService.focusFirstElementInContainer(); - }) - ); - } else if (this.url != null) { - this.testrunService.downloadZip(this.getZipLink(this.url), profile); - } - }); } @HostBinding('tabIndex') @@ -105,11 +81,6 @@ export class DownloadReportZipComponent this.tooltip.hide(); } - ngOnDestroy() { - this.destroy$.next(true); - this.destroy$.unsubscribe(); - } - ngOnInit() { if (this.data) { this.tooltip.message = `Download zip for Testrun # ${this.getTestRunId(this.data)}`; @@ -119,15 +90,8 @@ export class DownloadReportZipComponent constructor( datePipe: DatePipe, public dialog: MatDialog, - private testrunService: TestRunService, - private route: Router, - public tooltip: MatTooltip, - private focusManagerService: FocusManagerService + public tooltip: MatTooltip ) { super(datePipe); } - - private getZipLink(reportURL: string): string { - return reportURL.replace('report', 'export'); - } } diff --git a/modules/ui/src/app/components/download-report/download-report.component.spec.ts b/modules/ui/src/app/components/download-report/download-report.component.spec.ts index f29430f54..af711a26b 100644 --- a/modules/ui/src/app/components/download-report/download-report.component.spec.ts +++ b/modules/ui/src/app/components/download-report/download-report.component.spec.ts @@ -21,6 +21,7 @@ import { MOCK_PROGRESS_DATA_COMPLIANT, MOCK_PROGRESS_DATA_NON_COMPLIANT, } from '../../mocks/testrun.mock'; +import { TestrunStatus } from '../../model/testrun-status'; describe('DownloadReportComponent', () => { let component: DownloadReportComponent; @@ -39,13 +40,26 @@ describe('DownloadReportComponent', () => { expect(component).toBeTruthy(); }); - it('#getReportTitle should return data for download property of link', () => { - const expectedResult = - 'delta_03-din-cpu_1.2.2_compliant_22_jun_2023_9:20'; + describe('#getReportTitle', () => { + it('should return data for download property of link', () => { + const expectedResult = + 'delta_03-din-cpu_1.2.2_compliant_22_jun_2023_9:20'; - const result = component.getReportTitle(MOCK_PROGRESS_DATA_COMPLIANT); + const result = component.getReportTitle(MOCK_PROGRESS_DATA_COMPLIANT); - expect(result).toEqual(expectedResult); + expect(result).toEqual(expectedResult); + }); + + it('should return empty string if no device data', () => { + const MOCK_DATA_WITHOUT_DEVICE = { + ...MOCK_PROGRESS_DATA_COMPLIANT, + device: undefined as unknown, + } as TestrunStatus; + + const result = component.getReportTitle(MOCK_DATA_WITHOUT_DEVICE); + + expect(result).toEqual(''); + }); }); describe('#getClass', () => { diff --git a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.spec.ts b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.spec.ts index 728590ef8..74d0990e1 100644 --- a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.spec.ts +++ b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.spec.ts @@ -1,6 +1,15 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { + ComponentFixture, + discardPeriodicTasks, + fakeAsync, + TestBed, + tick, +} from '@angular/core/testing'; -import { DownloadZipModalComponent } from './download-zip-modal.component'; +import { + DialogCloseAction, + DownloadZipModalComponent, +} from './download-zip-modal.component'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { PROFILE_MOCK, @@ -10,31 +19,56 @@ import { import { of } from 'rxjs'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { TestRunService } from '../../services/test-run.service'; +import { Routes } from '../../model/routes'; +import { Router } from '@angular/router'; +import { FocusManagerService } from '../../services/focus-manager.service'; +import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; +import { RouterTestingModule } from '@angular/router/testing'; +import { Component } from '@angular/core'; describe('DownloadZipModalComponent', () => { + // @ts-expect-error data layer should be defined + window.dataLayer = window.dataLayer || []; let component: DownloadZipModalComponent; let fixture: ComponentFixture; - - const testRunServiceMock = jasmine.createSpyObj(['getRiskClass']); + let router: Router; + const testRunServiceMock = jasmine.createSpyObj('testRunServiceMock', [ + 'getRiskClass', + 'downloadZip', + ]); + const focusServiceMock: jasmine.SpyObj = + jasmine.createSpyObj('focusServiceMock', ['focusFirstElementInContainer']); + const actionBehaviorSubject$ = new BehaviorSubject({ + action: DialogCloseAction.Close, + }); beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [DownloadZipModalComponent, NoopAnimationsModule], + imports: [ + RouterTestingModule.withRoutes([ + { path: 'risk-assessment', component: FakeRiskAssessmentComponent }, + ]), + DownloadZipModalComponent, + NoopAnimationsModule, + ], providers: [ { provide: MatDialogRef, useValue: { keydownEvents: () => of(new KeyboardEvent('keydown', { code: '' })), close: () => ({}), + beforeClosed: () => actionBehaviorSubject$.asObservable(), }, }, { provide: MAT_DIALOG_DATA, useValue: { profiles: [PROFILE_MOCK_2, PROFILE_MOCK], + url: 'localhost:8080', }, }, { provide: TestRunService, useValue: testRunServiceMock }, + { provide: FocusManagerService, useValue: focusServiceMock }, ], }); }); @@ -44,11 +78,14 @@ describe('DownloadZipModalComponent', () => { TestBed.overrideProvider(MAT_DIALOG_DATA, { useValue: { profiles: [PROFILE_MOCK_2, PROFILE_MOCK, PROFILE_MOCK_3], + url: 'localhost:8080', + isPilot: true, }, }); TestBed.compileComponents(); fixture = TestBed.createComponent(DownloadZipModalComponent); + router = TestBed.get(Router); component = fixture.componentInstance; fixture.detectChanges(); }); @@ -65,33 +102,60 @@ describe('DownloadZipModalComponent', () => { ); }); - it('should close with null on redirect button click', async () => { - const closeSpy = spyOn(component.dialogRef, 'close'); - const redirectLink = fixture.nativeElement.querySelector( - '.redirect-link' - ) as HTMLAnchorElement; + it('should close with Redirect action on redirect button click', fakeAsync(() => { + const result = { + action: DialogCloseAction.Redirect, + }; + actionBehaviorSubject$.next(result); + fixture.detectChanges(); - redirectLink.click(); + fixture.ngZone?.run(() => { + const closeSpy = spyOn(component.dialogRef, 'close'); + const redirectLink = fixture.nativeElement.querySelector( + '.redirect-link' + ) as HTMLAnchorElement; - expect(closeSpy).toHaveBeenCalledWith(null); + redirectLink.click(); - closeSpy.calls.reset(); - }); + tick(2000); - it('should close with undefined on cancel button click', async () => { + expect(router.url).toBe(Routes.RiskAssessment); + expect( + focusServiceMock.focusFirstElementInContainer + ).toHaveBeenCalled(); + expect(closeSpy).toHaveBeenCalledWith(result); + + closeSpy.calls.reset(); + discardPeriodicTasks(); + }); + })); + + it('should close with Close action on cancel button click', async () => { + const result = { + action: DialogCloseAction.Close, + }; const closeSpy = spyOn(component.dialogRef, 'close'); + actionBehaviorSubject$.next(result); + fixture.detectChanges(); + const cancelButton = fixture.nativeElement.querySelector( '.cancel-button' ) as HTMLButtonElement; cancelButton.click(); - expect(closeSpy).toHaveBeenCalledWith(undefined); + expect(closeSpy).toHaveBeenCalledWith(result); closeSpy.calls.reset(); }); - it('should close with profile on download button click', async () => { + it('should close with Download action and profile on download button click', async () => { + const result = { + action: DialogCloseAction.Download, + profile: '', + }; + actionBehaviorSubject$.next(result); + fixture.detectChanges(); const closeSpy = spyOn(component.dialogRef, 'close'); const downloadButton = fixture.nativeElement.querySelector( '.download-button' @@ -99,8 +163,32 @@ describe('DownloadZipModalComponent', () => { downloadButton.click(); - expect(closeSpy).toHaveBeenCalledWith(''); + expect(closeSpy).toHaveBeenCalledWith(result); + expect(testRunServiceMock.downloadZip).toHaveBeenCalled(); + expect(router.url).not.toBe(Routes.RiskAssessment); + closeSpy.calls.reset(); + }); + + it('should send GA event if report is for Pilot program', async () => { + const result = { + action: DialogCloseAction.Download, + profile: '', + }; + actionBehaviorSubject$.next(result); + fixture.detectChanges(); + const closeSpy = spyOn(component.dialogRef, 'close'); + const downloadButton = fixture.nativeElement.querySelector( + '.download-button' + ) as HTMLButtonElement; + + downloadButton.click(); + expect( + // @ts-expect-error data layer should be defined + window.dataLayer.some( + (item: { event: string }) => item.event === 'pilot_download_zip' + ) + ).toBeTruthy(); closeSpy.calls.reset(); }); @@ -132,11 +220,13 @@ describe('DownloadZipModalComponent', () => { TestBed.overrideProvider(MAT_DIALOG_DATA, { useValue: { profiles: [], + url: 'localhost:8080', }, }); TestBed.compileComponents(); fixture = TestBed.createComponent(DownloadZipModalComponent); + router = TestBed.get(Router); component = fixture.componentInstance; fixture.detectChanges(); }); @@ -147,20 +237,35 @@ describe('DownloadZipModalComponent', () => { expect(select.classList.contains('mat-mdc-select-disabled')).toBeTruthy(); }); - it('should close with null on redirect button click', async () => { - const closeSpy = spyOn(component.dialogRef, 'close'); - const redirectLink = fixture.nativeElement.querySelector( - '.redirect-link' - ) as HTMLAnchorElement; + it('should close with Redirect action on redirect button click', fakeAsync(() => { + const result = { + action: DialogCloseAction.Redirect, + }; + actionBehaviorSubject$.next(result); + fixture.detectChanges(); - redirectLink.click(); + fixture.ngZone?.run(() => { + const closeSpy = spyOn(component.dialogRef, 'close'); + const redirectLink = fixture.nativeElement.querySelector( + '.redirect-link' + ) as HTMLAnchorElement; - expect(closeSpy).toHaveBeenCalledWith(null); + redirectLink.click(); - closeSpy.calls.reset(); - }); + tick(2000); + + expect(router.url).toBe(Routes.RiskAssessment); + expect( + focusServiceMock.focusFirstElementInContainer + ).toHaveBeenCalled(); + expect(closeSpy).toHaveBeenCalledWith(result); - it('should close with undefined on cancel button click', async () => { + closeSpy.calls.reset(); + discardPeriodicTasks(); + }); + })); + + it('should close with Close action on cancel button click', async () => { const closeSpy = spyOn(component.dialogRef, 'close'); const cancelButton = fixture.nativeElement.querySelector( '.cancel-button' @@ -168,7 +273,9 @@ describe('DownloadZipModalComponent', () => { cancelButton.click(); - expect(closeSpy).toHaveBeenCalledWith(undefined); + expect(closeSpy).toHaveBeenCalledWith({ + action: DialogCloseAction.Close, + }); closeSpy.calls.reset(); }); @@ -181,9 +288,18 @@ describe('DownloadZipModalComponent', () => { downloadButton.click(); - expect(closeSpy).toHaveBeenCalledWith(''); + expect(closeSpy).toHaveBeenCalledWith({ + action: DialogCloseAction.Download, + profile: '', + }); closeSpy.calls.reset(); }); }); }); + +@Component({ + selector: 'app-fake-risk-assessment-component', + template: '', +}) +class FakeRiskAssessmentComponent {} diff --git a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts index a703edb6d..199d89bf5 100644 --- a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts +++ b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts @@ -1,4 +1,10 @@ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + Inject, + OnDestroy, + OnInit, +} from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogActions, @@ -18,14 +24,29 @@ import { MatSelectModule } from '@angular/material/select'; import { MatOptionModule } from '@angular/material/core'; import { TestRunService } from '../../services/test-run.service'; import { Routes } from '../../model/routes'; -import { RouterLink } from '@angular/router'; +import { Router, RouterLink } from '@angular/router'; import { TestrunStatus, StatusOfTestrun } from '../../model/testrun-status'; import { DownloadReportComponent } from '../download-report/download-report.component'; +import { Subject, takeUntil, timer } from 'rxjs'; +import { FocusManagerService } from '../../services/focus-manager.service'; interface DialogData { profiles: Profile[]; testrunStatus?: TestrunStatus; isTestingComplete?: boolean; + url: string | null; + isPilot?: boolean; +} + +export enum DialogCloseAction { + Close, + Redirect, + Download, +} + +export interface DialogCloseResult { + action: DialogCloseAction; + profile: string | null | undefined; } @Component({ @@ -48,7 +69,11 @@ interface DialogData { styleUrl: './download-zip-modal.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class DownloadZipModalComponent extends EscapableDialogComponent { +export class DownloadZipModalComponent + extends EscapableDialogComponent + implements OnDestroy, OnInit +{ + private destroy$: Subject = new Subject(); readonly NO_PROFILE = { name: 'No Risk Profile selected', questions: [], @@ -60,7 +85,9 @@ export class DownloadZipModalComponent extends EscapableDialogComponent { constructor( private readonly testRunService: TestRunService, public override dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: DialogData + @Inject(MAT_DIALOG_DATA) public data: DialogData, + private route: Router, + private focusManagerService: FocusManagerService ) { super(dialogRef); this.profiles = data.profiles.filter( @@ -75,19 +102,70 @@ export class DownloadZipModalComponent extends EscapableDialogComponent { this.selectedProfile = this.profiles[0]; } + ngOnInit() { + this.dialogRef + ?.beforeClosed() + .pipe(takeUntil(this.destroy$)) + .subscribe((result: DialogCloseResult) => { + if (result.action === DialogCloseAction.Close) { + return; + } + if (result.action === DialogCloseAction.Redirect) { + this.route.navigate([Routes.RiskAssessment]).then(() => + timer(1000).subscribe(() => { + this.focusManagerService.focusFirstElementInContainer(); + }) + ); + return; + } + if (this.data.url != null && typeof result.profile === 'string') { + this.testRunService.downloadZip( + this.getZipLink(this.data.url), + result.profile + ); + if (this.data.isPilot) { + // @ts-expect-error data layer is not null + window.dataLayer.push({ + event: 'pilot_download_zip', + }); + } + } + }); + } + + ngOnDestroy() { + this.destroy$.next(true); + this.destroy$.unsubscribe(); + } + cancel(profile?: Profile | null) { if (profile === null) { - this.dialogRef.close(null); + this.dialogRef.close({ + action: DialogCloseAction.Redirect, + } as DialogCloseResult); + return; + } + if (!profile) { + this.dialogRef.close({ + action: DialogCloseAction.Close, + } as DialogCloseResult); return; } - let value = profile?.name; - if (profile && profile?.name === this.NO_PROFILE.name) { + let value = profile.name; + if (value === this.NO_PROFILE.name) { value = ''; } - this.dialogRef.close(value); + this.dialogRef.close({ + action: DialogCloseAction.Download, + profile: value, + } as DialogCloseResult); } public getRiskClass(riskResult: string): RiskResultClassName { return this.testRunService.getRiskClass(riskResult); } + + private getZipLink(reportURL: string): string { + return reportURL.replace('report', 'export'); + } } diff --git a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.html b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.html index 0ea7d3df4..cdbf27695 100644 --- a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.html +++ b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.html @@ -226,7 +226,7 @@ - {{ getOptionValue(option) }} +

{{ @@ -250,10 +250,13 @@ aria-label="{{ label }}" id="{{ formControlName }}-group" [formControlName]="formControlName"> + + {{ getControl(formControlName).value }} + - {{ getOptionValue(option) }} + [value]="getOptionValue(option)" + [innerHTML]="getSanitizedOptionValue(option)"> {{ diff --git a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.scss b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.scss index 14f7086f0..7fda3a91a 100644 --- a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.scss +++ b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.scss @@ -17,7 +17,7 @@ @import 'src/theming/colors'; @import 'src/theming/variables'; -::ng-deep .field-label { +.field-label { margin: 0; color: $grey-800; font-size: 18px; diff --git a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.ts b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.ts index 3d6b13016..d3e1fa3da 100644 --- a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.ts +++ b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.ts @@ -13,7 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Component, inject, Input, OnInit } from '@angular/core'; +import { + Component, + inject, + Input, + OnInit, + ViewEncapsulation, +} from '@angular/core'; import { FormControlType, OptionType, @@ -44,6 +50,7 @@ import { MatCheckboxModule } from '@angular/material/checkbox'; import { TextFieldModule } from '@angular/cdk/text-field'; import { DeviceValidators } from '../../pages/devices/components/device-form/device.validators'; import { ProfileValidators } from '../../pages/risk-assessment/profile-form/profile.validators'; +import { DomSanitizer } from '@angular/platform-browser'; @Component({ selector: 'app-dynamic-form', standalone: true, @@ -68,6 +75,7 @@ import { ProfileValidators } from '../../pages/risk-assessment/profile-form/prof ], templateUrl: './dynamic-form.component.html', styleUrl: './dynamic-form.component.scss', + encapsulation: ViewEncapsulation.None, }) export class DynamicFormComponent implements OnInit { public readonly FormControlType = FormControlType; @@ -83,7 +91,8 @@ export class DynamicFormComponent implements OnInit { constructor( private fb: FormBuilder, private deviceValidators: DeviceValidators, - private profileValidators: ProfileValidators + private profileValidators: ProfileValidators, + private domSanitizer: DomSanitizer ) {} getControl(name: string | number) { return this.formGroup.get(name.toString()) as AbstractControl; @@ -168,4 +177,10 @@ export class DynamicFormComponent implements OnInit { } return option; } + + getSanitizedOptionValue(option: OptionType) { + return this.domSanitizer.bypassSecurityTrustHtml( + this.getOptionValue(option) + ); + } } diff --git a/modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts b/modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts index ccf5b3335..45a34ddfc 100644 --- a/modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts +++ b/modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts @@ -28,6 +28,7 @@ import { MatIcon } from '@angular/material/icon'; padding-right: 4px; } .icon { + display: flex; width: 16px; height: 16px; line-height: 16px; diff --git a/modules/ui/src/app/components/report-action/report-action.component.spec.ts b/modules/ui/src/app/components/report-action/report-action.component.spec.ts index b4d69b149..7408de89d 100644 --- a/modules/ui/src/app/components/report-action/report-action.component.spec.ts +++ b/modules/ui/src/app/components/report-action/report-action.component.spec.ts @@ -2,6 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ReportActionComponent } from './report-action.component'; import { MOCK_PROGRESS_DATA_COMPLIANT } from '../../mocks/testrun.mock'; +import { TestrunStatus } from '../../model/testrun-status'; describe('ReportActionComponent', () => { let component: ReportActionComponent; @@ -21,12 +22,25 @@ describe('ReportActionComponent', () => { expect(component).toBeTruthy(); }); - it('#getTestRunId should return data for title of link', () => { - const expectedResult = 'Delta 03-DIN-CPU 1.2.2 22 Jun 2023 9:20'; + describe('#getTestRunId', () => { + it('should return data for title of link', () => { + const expectedResult = 'Delta 03-DIN-CPU 1.2.2 22 Jun 2023 9:20'; - const result = component.getTestRunId(MOCK_PROGRESS_DATA_COMPLIANT); + const result = component.getTestRunId(MOCK_PROGRESS_DATA_COMPLIANT); - expect(result).toEqual(expectedResult); + expect(result).toEqual(expectedResult); + }); + + it('should return title as empty string if no device data', () => { + const MOCK_DATA_WITHOUT_DEVICE = { + ...MOCK_PROGRESS_DATA_COMPLIANT, + device: undefined as unknown, + } as TestrunStatus; + + const result = component.getTestRunId(MOCK_DATA_WITHOUT_DEVICE); + + expect(result).toEqual(''); + }); }); it('#getFormattedDateString should return date as string in the format "d MMM y H:mm"', () => { diff --git a/modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts b/modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts index 30c3108e6..494b58823 100644 --- a/modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts +++ b/modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts @@ -6,25 +6,25 @@ import { } from '@angular/core/testing'; import { TestingCompleteComponent } from './testing-complete.component'; -import { TestRunService } from '../../services/test-run.service'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { Component } from '@angular/core'; import { MOCK_PROGRESS_DATA_COMPLIANT } from '../../mocks/testrun.mock'; import { of } from 'rxjs'; import { MatDialogRef } from '@angular/material/dialog'; -import { DownloadZipModalComponent } from '../download-zip-modal/download-zip-modal.component'; -import { Routes } from '../../model/routes'; +import { + DialogCloseAction, + DownloadZipModalComponent, +} from '../download-zip-modal/download-zip-modal.component'; +import { FocusManagerService } from '../../services/focus-manager.service'; describe('TestingCompleteComponent', () => { let component: TestingCompleteComponent; let fixture: ComponentFixture; - let router: Router; - - const testrunServiceMock: jasmine.SpyObj = - jasmine.createSpyObj('testrunServiceMock', ['downloadZip']); - + const mockFocusManagerService = jasmine.createSpyObj( + 'mockFocusManagerService', + ['focusFirstElementInContainer'] + ); beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ @@ -34,12 +34,13 @@ describe('TestingCompleteComponent', () => { TestingCompleteComponent, BrowserAnimationsModule, ], - providers: [{ provide: TestRunService, useValue: testrunServiceMock }], + providers: [ + { provide: FocusManagerService, useValue: mockFocusManagerService }, + ], }).compileComponents(); fixture = TestBed.createComponent(TestingCompleteComponent); component = fixture.componentInstance; - router = TestBed.get(Router); component.data = MOCK_PROGRESS_DATA_COMPLIANT; fixture.detectChanges(); }); @@ -49,16 +50,13 @@ describe('TestingCompleteComponent', () => { }); describe('#onInit', () => { - beforeEach(() => { - testrunServiceMock.downloadZip.calls.reset(); - }); - - it('should call downloadZip on service if profile is a string', fakeAsync(() => { + it('should focus first element in container when dialog closes with Close action', fakeAsync(() => { const openSpy = spyOn(component.dialog, 'open').and.returnValue({ - afterClosed: () => of(''), + afterClosed: () => of({ action: DialogCloseAction.Close }), } as MatDialogRef); component.ngOnInit(); + tick(1000); expect(openSpy).toHaveBeenCalledWith(DownloadZipModalComponent, { @@ -67,6 +65,8 @@ describe('TestingCompleteComponent', () => { profiles: [], testrunStatus: MOCK_PROGRESS_DATA_COMPLIANT, isTestingComplete: true, + url: 'https://api.testrun.io/report.pdf', + isPilot: false, }, autoFocus: 'first-tabbable', ariaDescribedBy: 'testing-result-main-info', @@ -75,10 +75,11 @@ describe('TestingCompleteComponent', () => { panelClass: 'initiate-test-run-dialog', }); - tick(); + tick(1000); - expect(testrunServiceMock.downloadZip).toHaveBeenCalled(); - expect(router.url).not.toBe(Routes.RiskAssessment); + expect( + mockFocusManagerService.focusFirstElementInContainer + ).toHaveBeenCalled(); openSpy.calls.reset(); })); }); diff --git a/modules/ui/src/app/components/testing-complete/testing-complete.component.ts b/modules/ui/src/app/components/testing-complete/testing-complete.component.ts index 28da57bee..79fc46c79 100644 --- a/modules/ui/src/app/components/testing-complete/testing-complete.component.ts +++ b/modules/ui/src/app/components/testing-complete/testing-complete.component.ts @@ -7,13 +7,15 @@ import { } from '@angular/core'; import { Subject, takeUntil, timer } from 'rxjs'; import { MatDialog } from '@angular/material/dialog'; -import { TestRunService } from '../../services/test-run.service'; -import { Router } from '@angular/router'; -import { DownloadZipModalComponent } from '../download-zip-modal/download-zip-modal.component'; -import { Routes } from '../../model/routes'; +import { + DialogCloseAction, + DialogCloseResult, + DownloadZipModalComponent, +} from '../download-zip-modal/download-zip-modal.component'; import { Profile } from '../../model/profile'; import { TestrunStatus } from '../../model/testrun-status'; import { FocusManagerService } from '../../services/focus-manager.service'; +import { TestingType } from '../../model/device'; @Component({ selector: 'app-testing-complete', @@ -29,15 +31,15 @@ export class TestingCompleteComponent implements OnDestroy, OnInit { constructor( public dialog: MatDialog, - private testrunService: TestRunService, - private route: Router, private focusManagerService: FocusManagerService ) {} ngOnInit() { - timer(1000).subscribe(() => { - this.openTestingCompleteModal(); - }); + timer(1000) + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + this.openTestingCompleteModal(); + }); } ngOnDestroy() { this.destroy$.next(true); @@ -51,6 +53,8 @@ export class TestingCompleteComponent implements OnDestroy, OnInit { profiles: this.profiles, testrunStatus: this.data, isTestingComplete: true, + url: this.data?.report, + isPilot: this.data?.device.test_pack === TestingType.Pilot, }, autoFocus: 'first-tabbable', ariaDescribedBy: 'testing-result-main-info', @@ -62,29 +66,14 @@ export class TestingCompleteComponent implements OnDestroy, OnInit { dialogRef ?.afterClosed() .pipe(takeUntil(this.destroy$)) - .subscribe(profile => { - if (profile === undefined) { - // close modal + .subscribe((result: DialogCloseResult) => { + if (result.action === DialogCloseAction.Close) { this.focusFirstElement(); return; } - if (profile === null) { - this.navigateToRiskAssessment(); - } else if (this.data?.report != null) { - this.testrunService.downloadZip( - this.getZipLink(this.data?.report), - profile - ); - } }); } - private navigateToRiskAssessment(): void { - this.route.navigate([Routes.RiskAssessment]).then(() => { - this.focusFirstElement(); - }); - } - private focusFirstElement() { timer(1000) .pipe(takeUntil(this.destroy$)) @@ -92,8 +81,4 @@ export class TestingCompleteComponent implements OnDestroy, OnInit { this.focusManagerService.focusFirstElementInContainer(); }); } - - private getZipLink(reportURL: string): string { - return reportURL.replace('report', 'export'); - } } diff --git a/modules/ui/src/app/mocks/settings.mock.ts b/modules/ui/src/app/mocks/settings.mock.ts index 49a11a895..53f092a0b 100644 --- a/modules/ui/src/app/mocks/settings.mock.ts +++ b/modules/ui/src/app/mocks/settings.mock.ts @@ -33,16 +33,21 @@ export const MOCK_SYSTEM_CONFIG_WITH_DATA: SystemConfig = { monitor_period: 600, }; -export const MOCK_INTERFACES: SystemInterfaces = { - mockDeviceKey: 'mockDeviceValue', - mockInternetKey: 'mockInternetValue', +export const MOCK_SYSTEM_CONFIG_WITH_SINGLE_PORT: SystemConfig = { + network: { + device_intf: 'mockDeviceKey', + internet_intf: '', + }, + log_level: 'DEBUG', + monitor_period: 600, + single_intf: true, }; -export const MOCK_INTERNET_OPTIONS: SystemInterfaces = { - '': 'Not specified', +export const MOCK_INTERFACES: SystemInterfaces = { mockDeviceKey: 'mockDeviceValue', mockInternetKey: 'mockInternetValue', }; + export const MOCK_DEVICE_VALUE: SystemInterfaces = { key: 'mockDeviceKey', value: 'mockDeviceValue', diff --git a/modules/ui/src/app/mocks/testrun.mock.ts b/modules/ui/src/app/mocks/testrun.mock.ts index c90927cd3..3decf9973 100644 --- a/modules/ui/src/app/mocks/testrun.mock.ts +++ b/modules/ui/src/app/mocks/testrun.mock.ts @@ -159,6 +159,12 @@ export const MOCK_PROGRESS_DATA_WAITING_FOR_DEVICE: TestrunStatus = { started: null, }; +export const MOCK_PROGRESS_DATA_VALIDATING: TestrunStatus = { + ...MOCK_PROGRESS_DATA_IN_PROGRESS, + status: StatusOfTestrun.Validating, + started: null, +}; + export const MOCK_PROGRESS_DATA_WITH_ERROR: TestrunStatus = PROGRESS_DATA_RESPONSE(StatusOfTestrun.InProgress, null, { ...TEST_DATA, diff --git a/modules/ui/src/app/model/testrun-status.ts b/modules/ui/src/app/model/testrun-status.ts index faa707f92..f14dce652 100644 --- a/modules/ui/src/app/model/testrun-status.ts +++ b/modules/ui/src/app/model/testrun-status.ts @@ -66,6 +66,7 @@ export enum StatusOfTestrun { Idle = 'Idle', Monitoring = 'Monitoring', Error = 'Error', + Validating = 'Validating Network', } export enum StatusOfTestResult { diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss index 3ce6ee85b..c2088c67b 100644 --- a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss +++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss @@ -52,10 +52,6 @@ $form-min-width: 732px; color: $primary; } -.device-form-mac-address-error { - white-space: nowrap; -} - .hidden { display: none; } @@ -306,10 +302,9 @@ $form-min-width: 732px; } :host mat-form-field { - &::ng-deep.mat-mdc-form-field-subscript-wrapper:has( - mat-error.error-multiline - ) { - height: 46px; + &::ng-deep.mat-mdc-form-field-error-wrapper { + margin-top: -20px; + position: static; } } diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts index e1b59025b..bcc34b35d 100644 --- a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts +++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts @@ -17,6 +17,7 @@ import { ComponentFixture, discardPeriodicTasks, fakeAsync, + flush, TestBed, tick, } from '@angular/core/testing'; @@ -50,17 +51,50 @@ import { DeviceStatus, TestingType } from '../../../../model/device'; import { Component, Input } from '@angular/core'; import { QuestionFormat } from '../../../../model/question'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; +import { selectDevices } from '../../../../store/selectors'; describe('DeviceQualificationFromComponent', () => { let component: DeviceQualificationFromComponent; let fixture: ComponentFixture; let compiled: HTMLElement; const testrunServiceMock: jasmine.SpyObj = - jasmine.createSpyObj('testrunServiceMock', ['fetchQuestionnaireFormat']); + jasmine.createSpyObj('testrunServiceMock', [ + 'fetchQuestionnaireFormat', + 'saveDevice', + ]); const keyboardEvent = new BehaviorSubject( new KeyboardEvent('keydown', { code: '' }) ); + const MOCK_DEVICE = { + status: DeviceStatus.VALID, + manufacturer: '', + model: '', + mac_addr: '', + test_pack: TestingType.Qualification, + type: '', + technology: '', + test_modules: { + udmi: { + enabled: true, + }, + connection: { + enabled: true, + }, + }, + additional_info: [ + { question: 'What type of device is this?', answer: '' }, + { + question: 'Does your device process any sensitive information? ', + answer: '', + }, + { + question: 'Please select the technology this device falls into', + answer: '', + }, + ], + }; + beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [FakeDynamicFormComponent], @@ -90,7 +124,9 @@ describe('DeviceQualificationFromComponent', () => { { provide: MAT_DIALOG_DATA, useValue: {} }, { provide: TestRunService, useValue: testrunServiceMock }, provideNgxMask(), - provideMockStore({}), + provideMockStore({ + selectors: [{ selector: selectDevices, value: [device, device] }], + }), ], }).compileComponents(); @@ -107,6 +143,8 @@ describe('DeviceQualificationFromComponent', () => { testrunServiceMock.fetchQuestionnaireFormat.and.returnValue( of(DEVICES_FORM) ); + + testrunServiceMock.saveDevice.and.returnValue(of(true)); }); it('should create', () => { @@ -160,6 +198,92 @@ describe('DeviceQualificationFromComponent', () => { closeSpy.calls.reset(); })); + it('should close dialog on submit with "Save" action', fakeAsync(() => { + component.device = MOCK_DEVICE; + const closeSpy = spyOn(component.dialogRef, 'close'); + fixture.detectChanges(); + + component.submit(); + tick(); + flush(); + + expect(closeSpy).toHaveBeenCalledWith({ + action: 'Save', + device: MOCK_DEVICE, + }); + + closeSpy.calls.reset(); + })); + + it('should close dialog on delete with "Delete" action', fakeAsync(() => { + const closeSpy = spyOn(component.dialogRef, 'close'); + fixture.detectChanges(); + + component.delete(); + tick(); + + expect(closeSpy).toHaveBeenCalledWith({ + action: 'Delete', + device: MOCK_DEVICE, + index: 0, + }); + + closeSpy.calls.reset(); + })); + + describe('#deviceHasNoChanges', () => { + const deviceProps = [ + { manufacturer: 'test' }, + { model: 'test' }, + { mac_addr: 'test' }, + { test_pack: TestingType.Pilot }, + { type: 'test' }, + { technology: 'test' }, + { + test_modules: { + udmi: { + enabled: false, + }, + }, + }, + { additional_info: undefined }, + { + additional_info: [ + { question: 'What type of device is this?', answer: 'test' }, + ], + }, + ]; + it('should return true if devices the same', () => { + const result = component.deviceHasNoChanges(MOCK_DEVICE, MOCK_DEVICE); + + expect(result).toBeTrue(); + }); + + deviceProps.forEach(item => { + it(`should return false if devices have different props`, () => { + const MOCK_DEVICE_1 = { ...MOCK_DEVICE, ...item }; + const result = component.deviceHasNoChanges(MOCK_DEVICE_1, MOCK_DEVICE); + + expect(result).toBeFalse(); + }); + }); + }); + + it('should trigger onResize method when window is resized ', () => { + fixture.detectChanges(); + const spyOnResize = spyOn(component, 'onResize'); + window.dispatchEvent(new Event('resize')); + fixture.detectChanges(); + expect(spyOnResize).toHaveBeenCalled(); + }); + + it('#goToStep should set selected index', () => { + fixture.detectChanges(); + component.goToStep(0); + + expect(component.stepper.selectedIndex).toBe(0); + }); + it('should close dialog on "cancel" click', () => { fixture.detectChanges(); component.manufacturer.setValue('test'); diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts index 67eb39d4c..09edf8cdc 100644 --- a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts +++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts @@ -501,7 +501,7 @@ export class DeviceQualificationFromComponent for (const key of keys1) { const val1 = device1.test_modules![key]; const val2 = device2.test_modules![key]; - if (val1.enabled !== val2.enabled) { + if (val1?.enabled !== val2?.enabled) { return false; } } diff --git a/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.spec.ts b/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.spec.ts index f85babe34..f7271c377 100644 --- a/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.spec.ts +++ b/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.spec.ts @@ -51,13 +51,27 @@ describe('DeleteReportComponent', () => { it('#deleteReport should open delete dialog', () => { const deviceRemovedSpy = spyOn(component.removeDevice, 'emit'); - spyOn(component.dialog, 'open').and.returnValue({ + const openSpy = spyOn(component.dialog, 'open').and.returnValue({ afterClosed: () => of(true), } as MatDialogRef); component.deleteReport(new Event('click')); expect(deviceRemovedSpy).toHaveBeenCalled(); + + expect(openSpy).toHaveBeenCalledWith(SimpleDialogComponent, { + ariaLabel: 'Delete report', + data: { + title: 'Delete report?', + content: + 'You are about to delete Delta 03-DIN-CPU 1.2.2 22 Jun 2023 9:20. Are you sure?', + }, + autoFocus: true, + hasBackdrop: true, + disableClose: true, + panelClass: 'simple-dialog', + }); + openSpy.calls.reset(); }); }); diff --git a/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.ts b/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.ts index 74abb27d1..96e178443 100644 --- a/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.ts +++ b/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.ts @@ -62,7 +62,7 @@ export class DeleteReportComponent ariaLabel: 'Delete report', data: { title: 'Delete report?', - content: this.getTestRunId(this.data), + content: `You are about to delete ${this.getTestRunId(this.data)}. Are you sure?`, }, autoFocus: true, hasBackdrop: true, diff --git a/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.spec.ts b/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.spec.ts index 34cb9459f..508bb7c54 100644 --- a/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.spec.ts +++ b/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.spec.ts @@ -36,6 +36,40 @@ describe('FilterChipsComponent', () => { expect(component).toBeTruthy(); }); + describe('#clearFilter', () => { + const MOCK_FILTERS = { + deviceInfo: 'Delta', + deviceFirmware: '03', + results: ['Compliant'], + dateRange: { start: '10/2/2024', end: '11/2/2024' }, + }; + + beforeEach(() => { + component.filters = MOCK_FILTERS; + }); + + it(`should clear deviceFirmware filter`, () => { + const result = { ...MOCK_FILTERS, deviceFirmware: '' }; + component.clearFilter('deviceFirmware'); + + expect(component.filters).toEqual(result); + }); + + it(`should clear results filter`, () => { + const clearedFilters = { ...MOCK_FILTERS, results: [] }; + component.clearFilter('results'); + + expect(component.filters).toEqual(clearedFilters); + }); + + it(`should clear dateRange filter`, () => { + const clearedFilters = { ...MOCK_FILTERS, dateRange: '' }; + component.clearFilter('dateRange'); + + expect(component.filters).toEqual(clearedFilters); + }); + }); + describe('DOM tests', () => { describe('"Clear all filters" button', () => { it('should exist', () => { diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html index dc1b0a330..8bde474ba 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html @@ -36,14 +36,12 @@ The Profile name is required - This Profile name is already used for another Risk Assessment - profile + This Profile name is already used for another profile - + +
diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts index 237f09160..d279a1f14 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts @@ -141,7 +141,7 @@ describe('ProfileFormComponent', () => { expect(error).toBeTruthy(); expect(nameError).toContain( - 'This Profile name is already used for another Risk Assessment profile' + 'This Profile name is already used for another profile' ); }); }); diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts index fe57d8867..2656221cd 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts @@ -84,6 +84,7 @@ export class ProfileFormComponent implements OnInit, AfterViewInit { @ViewChildren(CdkTextareaAutosize) autosize!: QueryList; @Input() profileFormat!: ProfileFormat[]; + @Input() isCopyProfile!: boolean; @Input() set profiles(profiles: Profile[]) { this.profileList = profiles; @@ -212,7 +213,7 @@ export class ProfileFormComponent implements OnInit, AfterViewInit { const request: any = { questions: [], }; - if (profile) { + if (profile && !this.isCopyProfile) { request.name = profile.name; request.rename = this.nameControl.value?.trim(); } else { diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts b/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts index 34bac3ebf..10280586d 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts @@ -36,13 +36,13 @@ export class ProfileValidators { profile: Profile | null ): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { - const value = control.value?.trim(); + const value = control.value?.trim().toLowerCase(); if ( value && profiles.length && (!profile || !profile.created || - (profile.created && profile?.name !== value)) + (profile.created && profile?.name.toLowerCase() !== value)) ) { const isSameProfileName = this.hasSameProfileName(value, profiles); return isSameProfileName ? { has_same_profile_name: true } : null; @@ -91,7 +91,8 @@ export class ProfileValidators { profiles: Profile[] ): boolean { return ( - profiles.some(profile => profile.name === profileName?.trim()) || false + profiles.some(profile => profile.name.toLowerCase() === profileName) || + false ); } } diff --git a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.spec.ts b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.spec.ts index 56f9ad6a4..ccdf0e211 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.spec.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.spec.ts @@ -13,7 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { + ComponentFixture, + fakeAsync, + TestBed, + tick, +} from '@angular/core/testing'; import { ProfileItemComponent } from './profile-item.component'; import { @@ -98,6 +103,32 @@ describe('ProfileItemComponent', () => { expect(profileClickedSpy).toHaveBeenCalledWith(PROFILE_MOCK); }); + it('should change tooltip on focusout', fakeAsync(() => { + component.profile = EXPIRED_PROFILE_MOCK; + fixture.detectChanges(); + + fixture.nativeElement.dispatchEvent(new Event('focusout')); + tick(); + + expect(component.tooltip.message).toEqual( + 'Expired. Please, create a new Risk profile.' + ); + })); + + it('#getRiskClass should call getRiskClass on testRunService', () => { + const MOCK_RISK = 'mock value'; + component.getRiskClass(MOCK_RISK); + expect(testRunServiceMock.getRiskClass).toHaveBeenCalledWith(MOCK_RISK); + }); + + it('#enterProfileItem should emit profileClicked', () => { + const profileClickedSpy = spyOn(component.profileClicked, 'emit'); + + component.enterProfileItem(PROFILE_MOCK); + + expect(profileClickedSpy).toHaveBeenCalled(); + }); + describe('with Expired profile', () => { beforeEach(() => { component.enterProfileItem(EXPIRED_PROFILE_MOCK); diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html index c5e38e360..620712a2f 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html @@ -23,6 +23,7 @@

Risk assessment

{ }); }); + it('#profileClicked should call openForm with profile', fakeAsync(() => { + spyOn(component, 'openForm'); + + component.profileClicked(PROFILE_MOCK); + tick(); + + expect(component.openForm).toHaveBeenCalledWith(PROFILE_MOCK); + })); + + it('#copyProfileAndOpenForm should call openForm with copy of profile', fakeAsync(() => { + spyOn(component, 'openForm'); + + component.copyProfileAndOpenForm(PROFILE_MOCK); + tick(); + + expect(component.openForm).toHaveBeenCalledWith(COPY_PROFILE_MOCK); + })); + describe('#saveProfile', () => { describe('with no profile selected', () => { beforeEach(() => { @@ -384,6 +402,7 @@ class FakeProfileItemComponent { }) class FakeProfileFormComponent { @Input() profiles!: Profile[]; + @Input() isCopyProfile!: boolean; @Input() selectedProfile!: Profile; @Input() profileFormat!: ProfileFormat[]; } diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts index b6eae14e2..e30efe710 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts @@ -40,6 +40,7 @@ import { SuccessDialogComponent } from './components/success-dialog/success-dial export class RiskAssessmentComponent implements OnInit, OnDestroy { viewModel$ = this.store.viewModel$; isOpenProfileForm = false; + isCopyProfile = false; private destroy$: Subject = new Subject(); constructor( private store: RiskAssessmentStore, @@ -71,6 +72,7 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { } async copyProfileAndOpenForm(profile: Profile) { + this.isCopyProfile = true; await this.openForm(this.getCopyOfProfile(profile)); } @@ -124,7 +126,10 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { this.liveAnnouncer.clear(); if (!selectedProfile) { this.saveProfile(profile, this.store.setFocusOnCreateButton); - } else if (this.compareProfiles(profile, selectedProfile)) { + } else if ( + this.compareProfiles(profile, selectedProfile) || + this.isCopyProfile + ) { this.saveProfile(profile, this.store.setFocusOnSelectedProfile); } else { this.openSaveDialog( @@ -187,6 +192,7 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { discard(selectedProfile: Profile | null) { this.liveAnnouncer.clear(); this.isOpenProfileForm = false; + this.isCopyProfile = false; if (selectedProfile) { timer(100).subscribe(() => { this.store.setFocusOnSelectedProfile(); @@ -220,6 +226,7 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { }, }); this.isOpenProfileForm = false; + this.isCopyProfile = false; } private setFocus(index: number): void { diff --git a/modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.scss b/modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.scss index 16093e336..38e82686c 100644 --- a/modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.scss +++ b/modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.scss @@ -48,6 +48,11 @@ .setting-field { width: 100%; + + &.mat-form-field-disabled { + opacity: 0.6; + } + ::ng-deep .mat-mdc-form-field-infix { min-height: 76px; display: flex; diff --git a/modules/ui/src/app/pages/settings/settings.component.html b/modules/ui/src/app/pages/settings/settings.component.html index 089ebd5eb..9a7671171 100644 --- a/modules/ui/src/app/pages/settings/settings.component.html +++ b/modules/ui/src/app/pages/settings/settings.component.html @@ -34,13 +34,6 @@

System settings

- - Warning! Testrun requires two ports to operate correctly. - - { fixture.detectChanges(); }); - it('should have callout component', () => { - const callout = compiled.querySelector('app-callout'); - - expect(callout).toBeTruthy(); - }); - it('should have disabled "Save" button', () => { component.deviceControl.setValue( MOCK_SYSTEM_CONFIG_WITH_DATA?.network?.device_intf @@ -331,7 +324,7 @@ describe('GeneralSettingsComponent', () => { isLessThanOneInterface: false, interfaces: MOCK_INTERFACES, deviceOptions: MOCK_INTERFACES, - internetOptions: MOCK_INTERNET_OPTIONS, + internetOptions: MOCK_INTERFACES, logLevelOptions: {}, monitoringPeriodOptions: {}, }); diff --git a/modules/ui/src/app/pages/settings/settings.component.ts b/modules/ui/src/app/pages/settings/settings.component.ts index b4fb17c9e..4c9d0dffe 100644 --- a/modules/ui/src/app/pages/settings/settings.component.ts +++ b/modules/ui/src/app/pages/settings/settings.component.ts @@ -83,7 +83,14 @@ export class SettingsComponent implements OnInit, OnDestroy { } get isFormValues(): boolean { - return this.internetControl?.value.value && this.deviceControl?.value.value; + return ( + this.deviceControl?.value?.value && + (this.isInternetControlDisabled || this.internetControl?.value?.value) + ); + } + + get isInternetControlDisabled(): boolean { + return this.internetControl?.disabled; } get isFormError(): boolean { @@ -102,7 +109,7 @@ export class SettingsComponent implements OnInit, OnDestroy { this.createSettingForm(); this.cleanFormErrorMessage(); this.settingsStore.getInterfaces(); - this.settingsStore.getSystemConfig(); + this.getSystemConfig(); this.setDefaultFormValues(); } @@ -112,7 +119,7 @@ export class SettingsComponent implements OnInit, OnDestroy { } this.showLoading(); this.getSystemInterfaces(); - this.settingsStore.getSystemConfig(); + this.getSystemConfig(); this.setDefaultFormValues(); } closeSetting(message: string): void { @@ -177,7 +184,7 @@ export class SettingsComponent implements OnInit, OnDestroy { const data: SystemConfig = { network: { device_intf: device_intf.key, - internet_intf: internet_intf.key, + internet_intf: this.isInternetControlDisabled ? '' : internet_intf.key, }, log_level: log_level.key, monitor_period: Number(monitor_period.key), diff --git a/modules/ui/src/app/pages/settings/settings.store.spec.ts b/modules/ui/src/app/pages/settings/settings.store.spec.ts index b51e1f2a6..969f5cb9f 100644 --- a/modules/ui/src/app/pages/settings/settings.store.spec.ts +++ b/modules/ui/src/app/pages/settings/settings.store.spec.ts @@ -13,12 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - DEFAULT_INTERNET_OPTION, - LOG_LEVELS, - MONITORING_PERIOD, - SettingsStore, -} from './settings.store'; +import { LOG_LEVELS, MONITORING_PERIOD, SettingsStore } from './settings.store'; import { TestRunService } from '../../services/test-run.service'; import SpyObj = jasmine.SpyObj; import { TestBed } from '@angular/core/testing'; @@ -39,11 +34,11 @@ import { MOCK_DEVICE_VALUE, MOCK_INTERFACE_VALUE, MOCK_INTERFACES, - MOCK_INTERNET_OPTIONS, MOCK_LOG_VALUE, MOCK_PERIOD_VALUE, MOCK_SYSTEM_CONFIG_WITH_DATA, MOCK_SYSTEM_CONFIG_WITH_NO_DATA, + MOCK_SYSTEM_CONFIG_WITH_SINGLE_PORT, } from '../../mocks/settings.mock'; describe('SettingsStore', () => { @@ -120,7 +115,6 @@ describe('SettingsStore', () => { expect(store.interfaces).toEqual(MOCK_INTERFACES); expect(store.deviceOptions).toEqual(MOCK_INTERFACES); expect(store.internetOptions).toEqual({ - '': 'Not specified', mockDeviceKey: 'mockDeviceValue', mockInternetKey: 'mockInternetValue', }); @@ -193,7 +187,7 @@ describe('SettingsStore', () => { settingsStore.viewModel$.pipe(skip(3), take(1)).subscribe(store => { expect(store.interfaces).toEqual(interfaces); expect(store.deviceOptions).toEqual(interfaces); - expect(store.internetOptions).toEqual(MOCK_INTERNET_OPTIONS); + expect(store.internetOptions).toEqual(interfaces); done(); }); @@ -279,6 +273,27 @@ describe('SettingsStore', () => { }); }); + describe('with single port mode', () => { + beforeEach(() => { + settingsStore.setSystemConfig(MOCK_SYSTEM_CONFIG_WITH_SINGLE_PORT); + settingsStore.setInterfaces(MOCK_INTERFACES); + }); + + it('should disable internet control', () => { + const form = fb.group({ + device_intf: ['value'], + internet_intf: [''], + log_level: [''], + monitor_period: ['value'], + }); + settingsStore.setDefaultFormValues(form); + + expect( + (form.get(FormKey.INTERNET) as FormControl).disabled + ).toBeTrue(); + }); + }); + describe('when values are empty', () => { beforeEach(() => { settingsStore.setSystemConfig(MOCK_SYSTEM_CONFIG_WITH_NO_DATA); @@ -300,7 +315,7 @@ describe('SettingsStore', () => { }); expect((form.get(FormKey.INTERNET) as FormControl).value).toEqual({ key: '', - value: DEFAULT_INTERNET_OPTION[''], + value: undefined, }); expect((form.get(FormKey.LOG_LEVEL) as FormControl).value).toEqual({ key: 'INFO', @@ -322,7 +337,6 @@ describe('SettingsStore', () => { mockNewInternetKey: 'mockNewInternetValue', }; const updateInternetOptions = { - '': 'Not specified', mockDeviceKey: 'mockDeviceValue', mockNewInternetKey: 'mockNewInternetValue', }; diff --git a/modules/ui/src/app/pages/settings/settings.store.ts b/modules/ui/src/app/pages/settings/settings.store.ts index fc4a00dc9..87071b222 100644 --- a/modules/ui/src/app/pages/settings/settings.store.ts +++ b/modules/ui/src/app/pages/settings/settings.store.ts @@ -46,10 +46,6 @@ export interface SettingsComponentState { monitoringPeriodOptions: SystemInterfaces; } -export const DEFAULT_INTERNET_OPTION = { - '': 'Not specified', -}; - export const LOG_LEVELS = { DEBUG: 'Every event will be logged', INFO: 'Normal events and issues', @@ -118,10 +114,7 @@ export class SettingsStore extends ComponentStore { ...state, interfaces, deviceOptions: interfaces, - internetOptions: { - ...DEFAULT_INTERNET_OPTION, - ...interfaces, - }, + internetOptions: interfaces, isLessThanOneInterface: Object.keys(interfaces).length < 1, }; }); @@ -180,16 +173,21 @@ export class SettingsStore extends ComponentStore { this.systemConfig$.pipe( withLatestFrom(this.deviceOptions$, this.internetOptions$), tap(([config, deviceOptions, internetOptions]) => { + if (config.single_intf) { + this.disableInternetInterface(formGroup); + } else { + this.setDefaultInternetInterfaceValue( + config.network?.internet_intf, + internetOptions, + formGroup + ); + } + this.setDefaultDeviceInterfaceValue( config.network?.device_intf, deviceOptions, formGroup ); - this.setDefaultInternetInterfaceValue( - config.network?.internet_intf, - internetOptions, - formGroup - ); this.setDefaultLogLevelValue( config.log_level, LOG_LEVELS, @@ -300,6 +298,11 @@ export class SettingsStore extends ComponentStore { ); } + private disableInternetInterface(formGroup: FormGroup) { + const internetControl = formGroup.get(FormKey.INTERNET) as FormControl; + internetControl.disable(); + } + private setDefaultValue( value: string | undefined, defaultValue: string | undefined, diff --git a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html index be4d3b259..9ca1d80b2 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html +++ b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html @@ -27,6 +27,10 @@

+ + @@ -105,6 +109,12 @@
+ +
+

Validating virtual network

+
+
+

diff --git a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.ts b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.ts index d84b692e1..101591c98 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.ts +++ b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.ts @@ -92,7 +92,8 @@ export class TestrunStatusCardComponent { return ( status === StatusOfTestrun.Monitoring || status === StatusOfTestrun.InProgress || - status === StatusOfTestrun.WaitingForDevice + status === StatusOfTestrun.WaitingForDevice || + status === StatusOfTestrun.Validating ); } diff --git a/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.scss b/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.scss index 231df35cd..cc5bc118b 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.scss +++ b/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.scss @@ -59,6 +59,7 @@ $expander-button-size: 28px; padding: 16px; letter-spacing: 0.2px; vertical-align: top; + font-weight: 400; } .tests-item-cell-result { diff --git a/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.spec.ts b/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.spec.ts index 0632ff75c..73b7196e8 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.spec.ts +++ b/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.spec.ts @@ -74,6 +74,14 @@ describe('ProgressTableComponent', () => { 'dns.network.hostname_resolutionCompliant' ); }); + + it('#getAriaLabel should return valid message', () => { + component.isAllCollapsed = true; + + const result = component.getAriaLabel(); + + expect(result).toEqual('Expand all rows'); + }); }); describe('DOM tests', () => { @@ -143,6 +151,12 @@ describe('ProgressTableComponent', () => { expect(button).not.toBeNull(); expect(button?.ariaLabel).toBe('Collapse row'); }); + + it('#checkAllCollapsed should return isAllCollapsed', () => { + component.checkAllCollapsed(); + + expect(component.isAllCollapsed).toBeFalse(); + }); }); }); }); diff --git a/modules/ui/src/app/pages/testrun/testrun.component.html b/modules/ui/src/app/pages/testrun/testrun.component.html index 042a7815f..92f8cf958 100644 --- a/modules/ui/src/app/pages/testrun/testrun.component.html +++ b/modules/ui/src/app/pages/testrun/testrun.component.html @@ -36,7 +36,10 @@ ">

-

+

{{ getTestRunName(data) }}

diff --git a/modules/ui/src/app/pages/testrun/testrun.component.scss b/modules/ui/src/app/pages/testrun/testrun.component.scss index 91f4c22d0..9e01bc725 100644 --- a/modules/ui/src/app/pages/testrun/testrun.component.scss +++ b/modules/ui/src/app/pages/testrun/testrun.component.scss @@ -90,6 +90,7 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + cursor: pointer; } .toolbar-tag-container { diff --git a/modules/ui/src/app/pages/testrun/testrun.component.ts b/modules/ui/src/app/pages/testrun/testrun.component.ts index 133abe532..447da1512 100644 --- a/modules/ui/src/app/pages/testrun/testrun.component.ts +++ b/modules/ui/src/app/pages/testrun/testrun.component.ts @@ -111,7 +111,7 @@ export class TestrunComponent implements OnInit, OnDestroy { getTestRunName(systemStatus: TestrunStatus): string { if (systemStatus?.device) { const device = systemStatus.device; - return `${device.manufacturer} ${device.model} v${device.firmware}`; + return `${device.manufacturer} ${device.model} ${device.firmware}`; } else { return ''; } diff --git a/modules/ui/src/app/pages/testrun/testrun.store.spec.ts b/modules/ui/src/app/pages/testrun/testrun.store.spec.ts index 449ffb408..24ffc6ba2 100644 --- a/modules/ui/src/app/pages/testrun/testrun.store.spec.ts +++ b/modules/ui/src/app/pages/testrun/testrun.store.spec.ts @@ -43,6 +43,7 @@ import { MOCK_PROGRESS_DATA_IN_PROGRESS_EMPTY, MOCK_PROGRESS_DATA_MONITORING, MOCK_PROGRESS_DATA_WAITING_FOR_DEVICE, + MOCK_PROGRESS_DATA_VALIDATING, TEST_DATA_RESULT_WITH_RECOMMENDATIONS, TEST_DATA_TABLE_RESULT, } from '../../mocks/testrun.mock'; @@ -171,6 +172,21 @@ describe('TestrunStore', () => { }); }); + it('should set value with empty values for status "Validating Network"', done => { + const expectedResult = EMPTY_RESULT; + + store.overrideSelector( + selectSystemStatus, + MOCK_PROGRESS_DATA_VALIDATING + ); + store.refreshState(); + + testrunStore.viewModel$.pipe(take(1)).subscribe(store => { + expect(store.dataSource).toEqual(expectedResult); + done(); + }); + }); + it('should set value with empty values for status "Cancelled" and empty result', done => { const expectedResult = EMPTY_RESULT; diff --git a/modules/ui/src/app/pages/testrun/testrun.store.ts b/modules/ui/src/app/pages/testrun/testrun.store.ts index 2dc0c19f1..351ec10d1 100644 --- a/modules/ui/src/app/pages/testrun/testrun.store.ts +++ b/modules/ui/src/app/pages/testrun/testrun.store.ts @@ -99,6 +99,7 @@ export class TestrunStore extends ComponentStore { // perform some additional actions tap(res => { if ( + res?.status === StatusOfTestrun.Validating || res?.status === StatusOfTestrun.WaitingForDevice || res?.status === StatusOfTestrun.Monitoring || (res?.status === StatusOfTestrun.InProgress && @@ -119,6 +120,7 @@ export class TestrunStore extends ComponentStore { tap(res => { const results = (res?.tests as TestsData)?.results || []; if ( + res?.status === StatusOfTestrun.Validating || res?.status === StatusOfTestrun.Monitoring || res?.status === StatusOfTestrun.WaitingForDevice || (res?.status === StatusOfTestrun.Cancelled && !results.length) @@ -208,7 +210,8 @@ export class TestrunStore extends ComponentStore { return ( status === StatusOfTestrun.InProgress || status === StatusOfTestrun.WaitingForDevice || - status === StatusOfTestrun.Monitoring + status === StatusOfTestrun.Monitoring || + status === StatusOfTestrun.Validating ); } diff --git a/modules/ui/src/app/services/notification.service.spec.ts b/modules/ui/src/app/services/notification.service.spec.ts index c02c45f46..175ed661e 100644 --- a/modules/ui/src/app/services/notification.service.spec.ts +++ b/modules/ui/src/app/services/notification.service.spec.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { TestBed } from '@angular/core/testing'; +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; import { NotificationService } from './notification.service'; import { @@ -25,6 +25,8 @@ import { of } from 'rxjs/internal/observable/of'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { AppState } from '../store/state'; import { SnackBarComponent } from '../components/snack-bar/snack-bar.component'; +import { setIsOpenWaitSnackBar } from '../store/actions'; +import { FocusManagerService } from './focus-manager.service'; describe('NotificationService', () => { let service: NotificationService; @@ -36,10 +38,14 @@ describe('NotificationService', () => { openFromComponent: () => ({}), }; + const focusServiceMock: jasmine.SpyObj = + jasmine.createSpyObj('focusServiceMock', ['focusFirstElementInContainer']); + beforeEach(() => { TestBed.configureTestingModule({ providers: [ { provide: MatSnackBar, useValue: mockMatSnackBar }, + { provide: FocusManagerService, useValue: focusServiceMock }, provideMockStore({}), ], }); @@ -84,6 +90,22 @@ describe('NotificationService', () => { politeness: 'assertive', }); }); + + it('should open snackbar with addition panelClass', () => { + const openSpy = spyOn(service.snackBar, 'open').and.returnValues({ + afterOpened: () => of(void 0), + afterDismissed: () => of({ dismissedByAction: true }), + } as MatSnackBarRef); + + service.notify('something good happened', 1500, 'mock-class'); + + expect(openSpy).toHaveBeenCalledWith('something good happened', 'OK', { + horizontalPosition: 'center', + panelClass: ['test-run-notification', 'mock-class'], + duration: 1500, + politeness: 'assertive', + }); + }); }); describe('openSnackBar', () => { @@ -103,6 +125,18 @@ describe('NotificationService', () => { panelClass: 'snack-bar-info', }); }); + + it('should call focusFirstElementInContainer', fakeAsync(() => { + spyOn(service.snackBar, 'openFromComponent').and.returnValues({ + afterOpened: () => of(void 0), + afterDismissed: () => of({ dismissedByAction: true }), + } as MatSnackBarRef); + + service.openSnackBar(); + tick(8000); + + expect(focusServiceMock.focusFirstElementInContainer).toHaveBeenCalled(); + })); }); describe('dismiss', () => { @@ -114,4 +148,21 @@ describe('NotificationService', () => { expect(matSnackBarSpy).toHaveBeenCalled(); }); }); + + it('#dismissSnackBar should dispatch setIsOpenWaitSnackBar action', () => { + service.dismissSnackBar(); + + expect(store.dispatch).toHaveBeenCalledWith( + setIsOpenWaitSnackBar({ isOpenWaitSnackBar: false }) + ); + }); + + it('#dismissWithTimout should call dismissSnackBar after timer', fakeAsync(() => { + const dismissSnackBarSpy = spyOn(service, 'dismissSnackBar').and.stub(); + + service.dismissWithTimout(); + tick(5000); + + expect(dismissSnackBarSpy).toHaveBeenCalledWith(); + })); }); diff --git a/modules/ui/src/app/services/test-run-mqtt.service.spec.ts b/modules/ui/src/app/services/test-run-mqtt.service.spec.ts index 19bda437a..6f5c7e325 100644 --- a/modules/ui/src/app/services/test-run-mqtt.service.spec.ts +++ b/modules/ui/src/app/services/test-run-mqtt.service.spec.ts @@ -9,17 +9,23 @@ import { MOCK_ADAPTERS } from '../mocks/settings.mock'; import { Topic } from '../model/topic'; import { MOCK_INTERNET } from '../mocks/topic.mock'; import { MOCK_PROGRESS_DATA_IN_PROGRESS } from '../mocks/testrun.mock'; +import { TestRunService } from './test-run.service'; describe('TestRunMqttService', () => { let service: TestRunMqttService; let mockService: SpyObj; + let testRunServiceMock: SpyObj; beforeEach(() => { mockService = jasmine.createSpyObj(['observe']); + testRunServiceMock = jasmine.createSpyObj(['changeReportURL']); TestBed.configureTestingModule({ imports: [MqttModule.forRoot(MQTT_SERVICE_OPTIONS)], - providers: [{ provide: MqttService, useValue: mockService }], + providers: [ + { provide: MqttService, useValue: mockService }, + { provide: TestRunService, useValue: testRunServiceMock }, + ], }); service = TestBed.inject(TestRunMqttService); }); @@ -75,6 +81,7 @@ describe('TestRunMqttService', () => { mockService.observe.and.returnValue( of(getResponse(MOCK_PROGRESS_DATA_IN_PROGRESS)) ); + testRunServiceMock.changeReportURL.and.returnValue(''); }); it('should subscribe the topic', done => { diff --git a/modules/ui/src/app/services/test-run-mqtt.service.ts b/modules/ui/src/app/services/test-run-mqtt.service.ts index d5e805da6..d53622d28 100644 --- a/modules/ui/src/app/services/test-run-mqtt.service.ts +++ b/modules/ui/src/app/services/test-run-mqtt.service.ts @@ -5,12 +5,16 @@ import { map } from 'rxjs/operators'; import { Adapters } from '../model/setting'; import { TestrunStatus } from '../model/testrun-status'; import { InternetConnection, Topic } from '../model/topic'; +import { TestRunService } from './test-run.service'; @Injectable({ providedIn: 'root', }) export class TestRunMqttService { - constructor(private mqttService: MqttService) {} + constructor( + private mqttService: MqttService, + private testrunService: TestRunService + ) {} getNetworkAdapters(): Observable { return this.topic(Topic.NetworkAdapters); @@ -21,7 +25,12 @@ export class TestRunMqttService { } getStatus(): Observable { - return this.topic(Topic.Status); + return this.topic(Topic.Status).pipe( + map(result => { + result.report = this.testrunService.changeReportURL(result.report); + return result; + }) + ); } private topic(topicName: string): Observable { diff --git a/modules/ui/src/app/services/test-run.service.spec.ts b/modules/ui/src/app/services/test-run.service.spec.ts index bcd3ec592..d50a9e8fc 100644 --- a/modules/ui/src/app/services/test-run.service.spec.ts +++ b/modules/ui/src/app/services/test-run.service.spec.ts @@ -154,6 +154,8 @@ describe('TestRunService', () => { }); describe('fetchSystemStatus', () => { + const systemStatusUrl = 'http://localhost:8000/system/status'; + it('should get system status data with no changes', () => { const result = { ...MOCK_PROGRESS_DATA_IN_PROGRESS }; @@ -161,12 +163,22 @@ describe('TestRunService', () => { expect(res).toEqual(result); }); - const req = httpTestingController.expectOne( - 'http://localhost:8000/system/status' - ); + const req = httpTestingController.expectOne(systemStatusUrl); expect(req.request.method).toBe('GET'); req.flush(result); }); + + it('should get system status as empty object if error happens', () => { + const mockError = { error: 'someError' } as ErrorEvent; + + service.fetchSystemStatus().subscribe(res => { + expect(res).toEqual({} as TestrunStatus); + }); + + const req = httpTestingController.expectOne(systemStatusUrl); + + req.error(mockError); + }); }); it('stopTestrun should have necessary request data', () => { @@ -354,6 +366,7 @@ describe('TestRunService', () => { StatusOfTestrun.InProgress, StatusOfTestrun.WaitingForDevice, StatusOfTestrun.Monitoring, + StatusOfTestrun.Validating, ]; const resultsNotInProgress = [ diff --git a/modules/ui/src/app/services/test-run.service.ts b/modules/ui/src/app/services/test-run.service.ts index 2e66234c7..d7539d2ab 100644 --- a/modules/ui/src/app/services/test-run.service.ts +++ b/modules/ui/src/app/services/test-run.service.ts @@ -53,6 +53,14 @@ export class TestRunService { constructor(private http: HttpClient) {} + changeReportURL(url: string): string { + if (!url) { + return ''; + } + // replace url part before '/report' from static to dynamic API URL + return url.replace(/^.*(?=\/report)/, `${API_URL}`); + } + fetchDevices(): Observable { return this.http.get(`${API_URL}/devices`); } @@ -72,7 +80,15 @@ export class TestRunService { } fetchSystemStatus() { - return this.http.get(`${API_URL}/system/status`); + return this.http.get(`${API_URL}/system/status`).pipe( + map(result => { + result.report = this.changeReportURL(result.report); + return result; + }), + catchError(() => { + return of({} as TestrunStatus); + }) + ); } stopTestrun(): Observable { @@ -134,7 +150,14 @@ export class TestRunService { } getHistory(): Observable { - return this.http.get(`${API_URL}/reports`); + return this.http.get(`${API_URL}/reports`).pipe( + map(result => { + result.forEach( + item => (item.report = this.changeReportURL(item.report)) + ); + return result; + }) + ); } public getResultClass(result: string): StatusResultClassName { @@ -169,7 +192,8 @@ export class TestRunService { return ( status === StatusOfTestrun.InProgress || status === StatusOfTestrun.WaitingForDevice || - status === StatusOfTestrun.Monitoring + status === StatusOfTestrun.Monitoring || + status === StatusOfTestrun.Validating ); } diff --git a/modules/ui/src/app/store/actions.ts b/modules/ui/src/app/store/actions.ts index b101ec499..58153ad9a 100644 --- a/modules/ui/src/app/store/actions.ts +++ b/modules/ui/src/app/store/actions.ts @@ -15,42 +15,22 @@ */ import { createAction, props } from '@ngrx/store'; -import { - Adapters, - InterfacesValidation, - SettingMissedError, - SystemConfig, -} from '../model/setting'; +import { Adapters, InterfacesValidation, SystemConfig } from '../model/setting'; import { SystemInterfaces } from '../model/setting'; import { Device, TestModule } from '../model/device'; import { TestrunStatus } from '../model/testrun-status'; import { Profile } from '../model/profile'; -// App component -export const toggleMenu = createAction('[App Component] Toggle Menu'); - -export const fetchInterfaces = createAction('[App Component] Fetch Interfaces'); - export const fetchInterfacesSuccess = createAction( '[App Component] Fetch interfaces Success', props<{ interfaces: SystemInterfaces }>() ); -export const updateFocusNavigation = createAction( - '[App Component] update focus navigation', - props<{ focusNavigation: boolean }>() -); - export const updateValidInterfaces = createAction( '[App Component] Update Valid Interfaces', props<{ validInterfaces: InterfacesValidation }>() ); -export const updateError = createAction( - '[App Component] Update Setting Missed Error', - props<{ settingMissedError: SettingMissedError }>() -); - // Settings export const fetchSystemConfigSuccess = createAction( '[Settings] Fetch System Config Success', diff --git a/modules/ui/src/app/store/effects.spec.ts b/modules/ui/src/app/store/effects.spec.ts index 01491b777..c0a06352d 100644 --- a/modules/ui/src/app/store/effects.spec.ts +++ b/modules/ui/src/app/store/effects.spec.ts @@ -28,11 +28,7 @@ import { Action } from '@ngrx/store'; import * as actions from './actions'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { AppState } from './state'; -import { - selectIsOpenWaitSnackBar, - selectMenuOpened, - selectSystemStatus, -} from './selectors'; +import { selectIsOpenWaitSnackBar, selectSystemStatus } from './selectors'; import { device, expired_device } from '../mocks/device.mock'; import { MOCK_PROGRESS_DATA_CANCELLING, @@ -191,191 +187,6 @@ describe('Effects', () => { }); }); - it('onMenuOpened$ should call updateFocusNavigation', done => { - actions$ = of(actions.toggleMenu()); - store.overrideSelector(selectMenuOpened, true); - - effects.onMenuOpened$.subscribe(action => { - expect(action).toEqual( - actions.updateFocusNavigation({ focusNavigation: true }) - ); - done(); - }); - }); - - describe('onValidateInterfaces$', () => { - it('should call updateError and set false if interfaces are not missed', done => { - actions$ = of( - actions.updateValidInterfaces({ - validInterfaces: { - deviceValid: true, - internetValid: true, - }, - }) - ); - - effects.onValidateInterfaces$.subscribe(action => { - expect(action).toEqual( - actions.updateError({ - settingMissedError: { - isSettingMissed: false, - devicePortMissed: false, - internetPortMissed: false, - }, - }) - ); - done(); - }); - }); - - it('should call updateError and set true if interfaces are missed', done => { - actions$ = of( - actions.updateValidInterfaces({ - validInterfaces: { - deviceValid: false, - internetValid: false, - }, - }) - ); - - effects.onValidateInterfaces$.subscribe(action => { - expect(action).toEqual( - actions.updateError({ - settingMissedError: { - isSettingMissed: true, - devicePortMissed: true, - internetPortMissed: true, - }, - }) - ); - done(); - }); - }); - }); - - describe('checkInterfacesInConfig$', () => { - it('should call updateValidInterfaces and set deviceValid as false if device interface is no longer available', done => { - actions$ = of( - actions.fetchInterfacesSuccess({ - interfaces: { - enx00e04c020fa8: '00:e0:4c:02:0f:a8', - enx207bd26205e9: '20:7b:d2:62:05:e9', - }, - }), - actions.fetchSystemConfigSuccess({ - systemConfig: { - network: { - device_intf: 'enx00e04c020fa2', - internet_intf: 'enx207bd26205e9', - }, - }, - }) - ); - - effects.checkInterfacesInConfig$.subscribe(action => { - expect(action).toEqual( - actions.updateValidInterfaces({ - validInterfaces: { - deviceValid: false, - internetValid: true, - }, - }) - ); - done(); - }); - }); - - it('should call updateValidInterfaces and set all true if interface is set and valid', done => { - actions$ = of( - actions.fetchInterfacesSuccess({ - interfaces: { - enx00e04c020fa8: '00:e0:4c:02:0f:a8', - enx207bd26205e9: '20:7b:d2:62:05:e9', - }, - }), - actions.fetchSystemConfigSuccess({ - systemConfig: { - network: { - device_intf: 'enx00e04c020fa8', - internet_intf: 'enx207bd26205e9', - }, - }, - }) - ); - - effects.checkInterfacesInConfig$.subscribe(action => { - expect(action).toEqual( - actions.updateValidInterfaces({ - validInterfaces: { - deviceValid: true, - internetValid: true, - }, - }) - ); - done(); - }); - }); - - it('should call updateValidInterfaces and set all true if interface are empty and config is not set', done => { - actions$ = of( - actions.fetchInterfacesSuccess({ - interfaces: {}, - }), - actions.fetchSystemConfigSuccess({ - systemConfig: { - network: { - device_intf: '', - internet_intf: '', - }, - }, - }) - ); - - effects.checkInterfacesInConfig$.subscribe(action => { - expect(action).toEqual( - actions.updateValidInterfaces({ - validInterfaces: { - deviceValid: true, - internetValid: true, - }, - }) - ); - done(); - }); - }); - - it('should call updateValidInterfaces and set all true if interface are not empty and config is not set', done => { - actions$ = of( - actions.fetchInterfacesSuccess({ - interfaces: { - enx00e04c020fa8: '00:e0:4c:02:0f:a8', - enx207bd26205e9: '20:7b:d2:62:05:e9', - }, - }), - actions.fetchSystemConfigSuccess({ - systemConfig: { - network: { - device_intf: '', - internet_intf: '', - }, - }, - }) - ); - - effects.checkInterfacesInConfig$.subscribe(action => { - expect(action).toEqual( - actions.updateValidInterfaces({ - validInterfaces: { - deviceValid: true, - internetValid: true, - }, - }) - ); - done(); - }); - }); - }); - describe('onFetchSystemConfigSuccess$', () => { it('should dispatch setHasConnectionSettings with true if device_intf is present', done => { actions$ = of( @@ -614,7 +425,7 @@ describe('Effects', () => { effects.checkStatusInReports$.subscribe(action => { expect(action).toEqual( - actions.setTestrunStatus({ systemStatus: IDLE_STATUS }) + actions.fetchSystemStatusSuccess({ systemStatus: IDLE_STATUS }) ); done(); }); diff --git a/modules/ui/src/app/store/effects.ts b/modules/ui/src/app/store/effects.ts index 045b0cc7c..12140eb65 100644 --- a/modules/ui/src/app/store/effects.ts +++ b/modules/ui/src/app/store/effects.ts @@ -24,7 +24,6 @@ import { AppState } from './state'; import { TestRunService } from '../services/test-run.service'; import { filter, - combineLatest, Subject, timer, take, @@ -32,11 +31,7 @@ import { EMPTY, Subscription, } from 'rxjs'; -import { - selectIsOpenWaitSnackBar, - selectMenuOpened, - selectSystemStatus, -} from './selectors'; +import { selectIsOpenWaitSnackBar, selectSystemStatus } from './selectors'; import { IDLE_STATUS, StatusOfTestrun, @@ -68,57 +63,6 @@ export class AppEffects { private internetSubscription: Subscription | undefined; private destroyWaitDeviceInterval$: Subject = new Subject(); - checkInterfacesInConfig$ = createEffect(() => - combineLatest([ - this.actions$.pipe(ofType(AppActions.fetchInterfacesSuccess)), - this.actions$.pipe(ofType(AppActions.fetchSystemConfigSuccess)), - ]).pipe( - filter( - ([ - , - { - systemConfig: { network }, - }, - ]) => network !== null - ), - map( - ([ - { interfaces }, - { - systemConfig: { network }, - }, - ]) => - AppActions.updateValidInterfaces({ - validInterfaces: { - deviceValid: - network?.device_intf == '' || - (!!network?.device_intf && !!interfaces[network.device_intf]), - internetValid: - network?.internet_intf == '' || - (!!network?.internet_intf && - !!interfaces[network.internet_intf]), - }, - }) - ) - ) - ); - - onValidateInterfaces$ = createEffect(() => { - return this.actions$.pipe( - ofType(AppActions.updateValidInterfaces), - map(({ validInterfaces }) => - AppActions.updateError({ - settingMissedError: { - isSettingMissed: - !validInterfaces.deviceValid || !validInterfaces.internetValid, - devicePortMissed: !validInterfaces.deviceValid, - internetPortMissed: !validInterfaces.internetValid, - }, - }) - ) - ); - }); - onFetchSystemConfigSuccess$ = createEffect(() => { return this.actions$.pipe( ofType(AppActions.fetchSystemConfigSuccess), @@ -134,15 +78,6 @@ export class AppEffects { ); }); - onMenuOpened$ = createEffect(() => { - return this.actions$.pipe( - ofType(AppActions.toggleMenu), - withLatestFrom(this.store.select(selectMenuOpened)), - filter(([, opened]) => opened === true), - map(() => AppActions.updateFocusNavigation({ focusNavigation: true })) // user will be navigated to side menu on tab - ); - }); - onSetDevices$ = createEffect(() => { return this.actions$.pipe( ofType(AppActions.setDevices), @@ -346,7 +281,9 @@ export class AppEffects { false ); }), - map(() => AppActions.setTestrunStatus({ systemStatus: IDLE_STATUS })) + map(() => + AppActions.fetchSystemStatusSuccess({ systemStatus: IDLE_STATUS }) + ) ); }); diff --git a/modules/ui/src/app/store/reducers.spec.ts b/modules/ui/src/app/store/reducers.spec.ts index 294de5fac..3411137fe 100644 --- a/modules/ui/src/app/store/reducers.spec.ts +++ b/modules/ui/src/app/store/reducers.spec.ts @@ -14,9 +14,10 @@ * limitations under the License. */ import * as fromReducer from './reducers'; -import { initialAppComponentState, initialSharedState } from './state'; +import { initialState as initialAppState } from './state'; import { fetchInterfacesSuccess, + fetchSystemConfigSuccess, setDeviceInProgress, setDevices, setHasConnectionSettings, @@ -33,10 +34,7 @@ import { setStatus, setTestModules, setTestrunStatus, - toggleMenu, updateAdapters, - updateError, - updateFocusNavigation, updateInternetConnection, } from './actions'; import { device, MOCK_TEST_MODULES } from '../mocks/device.mock'; @@ -48,11 +46,11 @@ import { MOCK_ADAPTERS } from '../mocks/settings.mock'; describe('Reducer', () => { describe('unknown action', () => { it('should return the default state', () => { - const initialState = initialAppComponentState; + const initialState = initialAppState; const action = { type: 'Unknown', }; - const state = fromReducer.appComponentReducer(initialState, action); + const state = fromReducer.rootReducer(initialState, action); expect(state).toBe(initialState); }); @@ -60,13 +58,13 @@ describe('Reducer', () => { describe('fetchInterfacesSuccess action', () => { it('should update state', () => { - const initialState = initialAppComponentState; + const initialState = initialAppState; const newInterfaces = { enx00e04c020fa8: '00:e0:4c:02:0f:a8', enx207bd26205e9: '20:7b:d2:62:05:e9', }; const action = fetchInterfacesSuccess({ interfaces: newInterfaces }); - const state = fromReducer.appComponentReducer(initialState, action); + const state = fromReducer.rootReducer(initialState, action); const newState = { ...initialState, ...{ interfaces: newInterfaces } }; expect(state).toEqual(newState); @@ -74,33 +72,9 @@ describe('Reducer', () => { }); }); - describe('updateFocusNavigation action', () => { - it('should update state', () => { - const initialState = initialAppComponentState; - const action = updateFocusNavigation({ focusNavigation: true }); - const state = fromReducer.appComponentReducer(initialState, action); - - const newState = { ...initialState, ...{ focusNavigation: true } }; - expect(state).toEqual(newState); - expect(state).not.toBe(initialState); - }); - }); - - describe('toggleMenu action', () => { - it('should update state', () => { - const initialState = initialAppComponentState; - const action = toggleMenu(); - const state = fromReducer.appComponentReducer(initialState, action); - - const newState = { ...initialState, ...{ isMenuOpen: true } }; - expect(state).toEqual(newState); - expect(state).not.toBe(initialState); - }); - }); - describe('setHasConnectionSettings action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setHasConnectionSettings({ hasConnectionSettings: true }); const state = fromReducer.sharedReducer(initialState, action); const newState = { ...initialState, ...{ hasConnectionSettings: true } }; @@ -110,31 +84,9 @@ describe('Reducer', () => { }); }); - describe('updateError action', () => { - it('should update state', () => { - const mockSettingMissedError = { - isSettingMissed: true, - devicePortMissed: true, - internetPortMissed: true, - }; - const initialState = initialAppComponentState; - const action = updateError({ - settingMissedError: mockSettingMissedError, - }); - const state = fromReducer.appComponentReducer(initialState, action); - const newState = { - ...initialState, - ...{ settingMissedError: mockSettingMissedError }, - }; - - expect(state).toEqual(newState); - expect(state).not.toBe(initialState); - }); - }); - describe('setIsOpenAddDevice action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setIsOpenAddDevice({ isOpenAddDevice: true }); const state = fromReducer.sharedReducer(initialState, action); const newState = { ...initialState, ...{ isOpenAddDevice: true } }; @@ -146,7 +98,7 @@ describe('Reducer', () => { describe('setIsOpenWaitSnackBar action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setIsOpenWaitSnackBar({ isOpenWaitSnackBar: true }); const state = fromReducer.sharedReducer(initialState, action); const newState = { ...initialState, ...{ isOpenWaitSnackBar: true } }; @@ -158,7 +110,7 @@ describe('Reducer', () => { describe('setHasDevices action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setHasDevices({ hasDevices: true }); const state = fromReducer.sharedReducer(initialState, action); const newState = { ...initialState, ...{ hasDevices: true } }; @@ -170,7 +122,7 @@ describe('Reducer', () => { describe('setHasExpiredDevices action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setHasExpiredDevices({ hasExpiredDevices: true }); const state = fromReducer.sharedReducer(initialState, action); const newState = { ...initialState, ...{ hasExpiredDevices: true } }; @@ -182,7 +134,7 @@ describe('Reducer', () => { describe('setIsAllDevicesOutdated action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setIsAllDevicesOutdated({ isAllDevicesOutdated: true }); const state = fromReducer.sharedReducer(initialState, action); const newState = { ...initialState, ...{ isAllDevicesOutdated: true } }; @@ -194,7 +146,7 @@ describe('Reducer', () => { describe('setDevices action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const devices = [device, device]; const action = setDevices({ devices }); const state = fromReducer.sharedReducer(initialState, action); @@ -207,7 +159,7 @@ describe('Reducer', () => { describe('setHasRiskProfiles action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setHasRiskProfiles({ hasRiskProfiles: true }); const state = fromReducer.sharedReducer(initialState, action); const newState = { ...initialState, ...{ hasRiskProfiles: true } }; @@ -219,7 +171,7 @@ describe('Reducer', () => { describe('setRiskProfiles action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const riskProfiles = [PROFILE_MOCK]; const action = setRiskProfiles({ riskProfiles }); const state = fromReducer.sharedReducer(initialState, action); @@ -232,7 +184,7 @@ describe('Reducer', () => { describe('setDeviceInProgress action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const deviceInProgress = device; const action = setDeviceInProgress({ device: deviceInProgress }); const state = fromReducer.sharedReducer(initialState, action); @@ -248,7 +200,7 @@ describe('Reducer', () => { describe('setTestrunStatus action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setTestrunStatus({ systemStatus: MOCK_PROGRESS_DATA_CANCELLING, }); @@ -265,7 +217,7 @@ describe('Reducer', () => { describe('setIsTestingComplete action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setIsTestingComplete({ isTestingComplete: true, }); @@ -282,7 +234,7 @@ describe('Reducer', () => { describe('setIsOpenStartTestrun action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setIsOpenStartTestrun({ isOpenStartTestrun: true }); const state = fromReducer.sharedReducer(initialState, action); const newState = { ...initialState, ...{ isOpenStartTestrun: true } }; @@ -294,7 +246,7 @@ describe('Reducer', () => { describe('setStatus action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setStatus({ status: MOCK_PROGRESS_DATA_CANCELLING.status, }); @@ -311,7 +263,7 @@ describe('Reducer', () => { describe('setReports action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setReports({ reports: HISTORY, }); @@ -328,7 +280,7 @@ describe('Reducer', () => { describe('setTestModules action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setTestModules({ testModules: MOCK_TEST_MODULES, }); @@ -345,7 +297,7 @@ describe('Reducer', () => { describe('updateAdapters action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = updateAdapters({ adapters: MOCK_ADAPTERS, }); @@ -362,7 +314,7 @@ describe('Reducer', () => { describe('updateInternetConnection action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = updateInternetConnection({ internetConnection: true }); const state = fromReducer.sharedReducer(initialState, action); const newState = { ...initialState, ...{ internetConnection: true } }; @@ -371,4 +323,22 @@ describe('Reducer', () => { expect(state).not.toBe(initialState); }); }); + + describe('fetchSystemConfigSuccess action', () => { + it('should update state', () => { + const initialState = initialAppState; + + const action = fetchSystemConfigSuccess({ + systemConfig: { network: {} }, + }); + const state = fromReducer.rootReducer(initialState, action); + + const newState = { + ...initialState, + ...{ systemConfig: { network: {} } }, + }; + expect(state).toEqual(newState); + expect(state).not.toBe(initialState); + }); + }); }); diff --git a/modules/ui/src/app/store/reducers.ts b/modules/ui/src/app/store/reducers.ts index bc7350c0b..e51fde1b7 100644 --- a/modules/ui/src/app/store/reducers.ts +++ b/modules/ui/src/app/store/reducers.ts @@ -13,34 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { combineReducers, createReducer, on } from '@ngrx/store'; +import { createReducer, on } from '@ngrx/store'; import * as Actions from './actions'; -import { initialAppComponentState, initialSharedState } from './state'; +import { initialState } from './state'; export const appFeatureKey = 'app'; -export const appComponentReducer = createReducer( - initialAppComponentState, - on(Actions.toggleMenu, state => ({ - ...state, - isMenuOpen: !state.isMenuOpen, - })), - on(Actions.fetchInterfacesSuccess, (state, { interfaces }) => ({ - ...state, - interfaces, - })), - on(Actions.updateFocusNavigation, (state, { focusNavigation }) => ({ - ...state, - focusNavigation, - })), - on(Actions.updateError, (state, { settingMissedError }) => ({ - ...state, - settingMissedError, - })) -); - export const sharedReducer = createReducer( - initialSharedState, + initialState, on(Actions.setHasConnectionSettings, (state, { hasConnectionSettings }) => { return { ...state, @@ -148,10 +128,15 @@ export const sharedReducer = createReducer( ...state, internetConnection, }; - }) + }), + on(Actions.fetchInterfacesSuccess, (state, { interfaces }) => ({ + ...state, + interfaces, + })), + on(Actions.fetchSystemConfigSuccess, (state, { systemConfig }) => ({ + ...state, + systemConfig, + })) ); -export const rootReducer = combineReducers({ - appComponent: appComponentReducer, - shared: sharedReducer, -}); +export const rootReducer = sharedReducer; diff --git a/modules/ui/src/app/store/selectors.spec.ts b/modules/ui/src/app/store/selectors.spec.ts index 3919fd349..ba6a52315 100644 --- a/modules/ui/src/app/store/selectors.spec.ts +++ b/modules/ui/src/app/store/selectors.spec.ts @@ -19,15 +19,12 @@ import { selectAdapters, selectDeviceInProgress, selectDevices, - selectError, selectHasConnectionSettings, selectHasDevices, selectHasRiskProfiles, - selectInterfaces, selectIsOpenAddDevice, selectIsOpenStartTestrun, selectIsOpenWaitSnackBar, - selectMenuOpened, selectReports, selectRiskProfiles, selectStatus, @@ -37,46 +34,35 @@ import { selectInternetConnection, selectIsAllDevicesOutdated, selectIsTestingComplete, + selectInterfaces, + selectSystemConfig, } from './selectors'; describe('Selectors', () => { const initialState: AppState = { - appComponent: { - isMenuOpen: false, - interfaces: {}, - isStatusLoaded: false, - devicesLength: 0, - focusNavigation: false, - settingMissedError: null, - }, - shared: { - hasConnectionSettings: false, - isAllDevicesOutdated: false, - devices: [], - hasDevices: false, - hasExpiredDevices: false, - isOpenAddDevice: false, - riskProfiles: [], - hasRiskProfiles: false, - isStopTestrun: false, - isOpenWaitSnackBar: false, - isOpenStartTestrun: false, - systemStatus: null, - deviceInProgress: null, - status: null, - isTestingComplete: false, - reports: [], - testModules: [], - adapters: {}, - internetConnection: null, - }, + hasConnectionSettings: false, + isAllDevicesOutdated: false, + devices: [], + hasDevices: false, + hasExpiredDevices: false, + isOpenAddDevice: false, + riskProfiles: [], + hasRiskProfiles: false, + isStopTestrun: false, + isOpenWaitSnackBar: false, + isOpenStartTestrun: false, + systemStatus: null, + deviceInProgress: null, + status: null, + isTestingComplete: false, + reports: [], + testModules: [], + adapters: {}, + internetConnection: null, + interfaces: {}, + systemConfig: { network: {} }, }; - it('should select the is menu opened', () => { - const result = selectMenuOpened.projector(initialState); - expect(result).toEqual(false); - }); - it('should select interfaces', () => { const result = selectInterfaces.projector(initialState); expect(result).toEqual({}); @@ -87,11 +73,6 @@ describe('Selectors', () => { expect(result).toEqual(false); }); - it('should select settingMissedError', () => { - const result = selectError.projector(initialState); - expect(result).toEqual(null); - }); - it('should select devices', () => { const result = selectDevices.projector(initialState); expect(result).toEqual([]); @@ -176,4 +157,9 @@ describe('Selectors', () => { const result = selectInternetConnection.projector(initialState); expect(result).toEqual(null); }); + + it('should select systemConfig', () => { + const result = selectSystemConfig.projector(initialState); + expect(result).toEqual({ network: {} }); + }); }); diff --git a/modules/ui/src/app/store/selectors.ts b/modules/ui/src/app/store/selectors.ts index 0fec7b492..2a3fb0ed9 100644 --- a/modules/ui/src/app/store/selectors.ts +++ b/modules/ui/src/app/store/selectors.ts @@ -22,108 +22,103 @@ export const selectAppState = createFeatureSelector( fromApp.appFeatureKey ); -export const selectMenuOpened = createSelector( - selectAppState, - (state: AppState) => state.appComponent.isMenuOpen -); - export const selectInterfaces = createSelector( selectAppState, - (state: AppState) => state.appComponent.interfaces + (state: AppState) => state.interfaces ); export const selectHasConnectionSettings = createSelector( selectAppState, - (state: AppState) => state.shared.hasConnectionSettings + (state: AppState) => state.hasConnectionSettings ); export const selectIsOpenAddDevice = createSelector( selectAppState, - (state: AppState) => state.shared.isOpenAddDevice + (state: AppState) => state.isOpenAddDevice ); export const selectHasDevices = createSelector( selectAppState, - (state: AppState) => state.shared.hasDevices + (state: AppState) => state.hasDevices ); export const selectHasExpiredDevices = createSelector( selectAppState, - (state: AppState) => state.shared.hasExpiredDevices + (state: AppState) => state.hasExpiredDevices ); export const selectIsAllDevicesOutdated = createSelector( selectAppState, - (state: AppState) => state.shared.isAllDevicesOutdated + (state: AppState) => state.isAllDevicesOutdated ); export const selectDevices = createSelector( selectAppState, - (state: AppState) => state.shared.devices + (state: AppState) => state.devices ); export const selectDeviceInProgress = createSelector( selectAppState, - (state: AppState) => state.shared.deviceInProgress + (state: AppState) => state.deviceInProgress ); export const selectHasRiskProfiles = createSelector( selectAppState, - (state: AppState) => state.shared.hasRiskProfiles + (state: AppState) => state.hasRiskProfiles ); export const selectRiskProfiles = createSelector( selectAppState, - (state: AppState) => state.shared.riskProfiles -); - -export const selectError = createSelector( - selectAppState, - (state: AppState) => state.appComponent.settingMissedError + (state: AppState) => state.riskProfiles ); export const selectSystemStatus = createSelector( selectAppState, (state: AppState) => { - return state.shared.systemStatus; + return state.systemStatus; } ); export const selectIsTestingComplete = createSelector( selectAppState, - (state: AppState) => state.shared.isTestingComplete + (state: AppState) => state.isTestingComplete ); export const selectIsOpenWaitSnackBar = createSelector( selectAppState, - (state: AppState) => state.shared.isOpenWaitSnackBar + (state: AppState) => state.isOpenWaitSnackBar ); export const selectIsOpenStartTestrun = createSelector( selectAppState, - (state: AppState) => state.shared.isOpenStartTestrun + (state: AppState) => state.isOpenStartTestrun ); export const selectStatus = createSelector( selectAppState, - (state: AppState) => state.shared.status + (state: AppState) => state.status ); export const selectReports = createSelector( selectAppState, - (state: AppState) => state.shared.reports + (state: AppState) => state.reports ); export const selectTestModules = createSelector( selectAppState, - (state: AppState) => state.shared.testModules + (state: AppState) => state.testModules ); export const selectAdapters = createSelector( selectAppState, - (state: AppState) => state.shared.adapters + (state: AppState) => state.adapters ); export const selectInternetConnection = createSelector( selectAppState, - (state: AppState) => state.shared.internetConnection + (state: AppState) => state.internetConnection +); + +export const selectSystemConfig = createSelector( + selectAppState, + (state: AppState) => state.systemConfig ); diff --git a/modules/ui/src/app/store/state.ts b/modules/ui/src/app/store/state.ts index a0a993fd1..2ce978b6f 100644 --- a/modules/ui/src/app/store/state.ts +++ b/modules/ui/src/app/store/state.ts @@ -15,31 +15,13 @@ */ import { TestrunStatus } from '../model/testrun-status'; import { Device, TestModule } from '../model/device'; -import { - Adapters, - SettingMissedError, - SystemInterfaces, -} from '../model/setting'; +import { Adapters, SystemConfig, SystemInterfaces } from '../model/setting'; import { Profile } from '../model/profile'; export interface AppState { - appComponent: AppComponentState; - shared: SharedState; -} - -export interface AppComponentState { - isMenuOpen: boolean; + // app, settings interfaces: SystemInterfaces; - /** - * Indicates, if side menu should be focused on keyboard navigation after menu is opened - */ - focusNavigation: boolean; - settingMissedError: SettingMissedError | null; - isStatusLoaded: boolean; // TODO should be updated in effect when fetch status - devicesLength: number; // TODO should be renamed to focusToggleSettingsBtn (true when devices.length > 0) and updated in effect when fetch device -} - -export interface SharedState { + systemConfig: SystemConfig; devices: Device[]; //used in app, devices, testrun hasDevices: boolean; @@ -67,16 +49,7 @@ export interface SharedState { internetConnection: boolean | null; } -export const initialAppComponentState: AppComponentState = { - isMenuOpen: false, - interfaces: {}, - focusNavigation: false, - isStatusLoaded: false, - devicesLength: 0, - settingMissedError: null, -}; - -export const initialSharedState: SharedState = { +export const initialState: AppState = { hasConnectionSettings: null, isOpenAddDevice: false, isStopTestrun: false, @@ -96,4 +69,6 @@ export const initialSharedState: SharedState = { testModules: [], adapters: {}, internetConnection: null, + interfaces: {}, + systemConfig: { network: {} }, }; diff --git a/modules/ws/ws.Dockerfile b/modules/ws/ws.Dockerfile index 7e9408a47..606cc333e 100644 --- a/modules/ws/ws.Dockerfile +++ b/modules/ws/ws.Dockerfile @@ -1,4 +1,4 @@ -FROM eclipse-mosquitto:2.0.18 +FROM eclipse-mosquitto@sha256:4a46c840adf48e7acd49883206a5c075c14ec95845ee6d30ba935a6719d6b41c RUN mkdir -p /mosquitto/data/ COPY modules/ws/conf/mosquitto.conf /mosquitto/config/mosquitto.conf VOLUME /mosquitto/data/ \ No newline at end of file diff --git a/resources/report/test_report_styles.css b/resources/report/test_report_styles.css index f261cecb3..b1ed9d33c 100644 --- a/resources/report/test_report_styles.css +++ b/resources/report/test_report_styles.css @@ -174,10 +174,16 @@ margin-bottom: 25px; } + .module-summary.not-first{ + margin-top: 10px; + } + + .module-summary thead tr th { text-align: left; padding-top: 15px; - padding-left: 15px; + padding-left: 10px; + padding-right: 5px; font-weight: 500; color: #5F6368; font-size: 14px; @@ -185,8 +191,9 @@ .module-summary tbody tr td { padding-bottom: 15px; - padding-left: 15px; - font-size: 24px; + padding-left: 10px; + padding-right: 5px; + font-size: 22px; } .module-data { @@ -211,6 +218,8 @@ font-weight: 400; border-top: 1px solid #DADCE0; font-family: 'Roboto Mono', monospace; + word-wrap: break-word; + word-break: break-word; } div.steps-to-resolve { diff --git a/resources/report/test_report_template.html b/resources/report/test_report_template.html index 24a082bf8..fbd8d1c68 100644 --- a/resources/report/test_report_template.html +++ b/resources/report/test_report_template.html @@ -115,7 +115,7 @@

Results List ({{ successful_tests }}/{{ tot {% set page_index.value = page_index.value+1 %}
{{ header_macros.header(False, "Testrun report", json_data, device, logo, icon_qualification, icon_pilot)}} -

Non-complaint tests and suggested steps to resolve

+

Non-compliant tests and suggested steps to resolve

{% for step in steps_to_resolve %}
diff --git a/resources/risk_assessment.json b/resources/risk_assessment.json index a4c95be95..d4f2574fb 100644 --- a/resources/risk_assessment.json +++ b/resources/risk_assessment.json @@ -1,7 +1,7 @@ [ { "question": "How will this device be used at Google?", - "description": "Desribe your use case. Add links to user journey diagrams and TDD if available.", + "description": "Describe your use case. Add links to user journey diagrams and TDD if available.", "type": "text-long", "validation": { "max": "512", @@ -60,7 +60,7 @@ "risk": "High" }, { - "text": "A failure in data transmission would likely have a substantial negative impact (https://www.rra.rocks/docs/standard_levels#levels-definitions)", + "text": "A failure in data transmission would likely have a substantial negative impact (https://www.rra.rocks/docs/standard_levels#levels-definitions)", "risk": "High" }, { diff --git a/testing/api/test_api.py b/testing/api/test_api.py index 71df07b6b..e67506a71 100644 --- a/testing/api/test_api.py +++ b/testing/api/test_api.py @@ -25,6 +25,8 @@ import time import pytest import requests +from cryptography import x509 +from cryptography.hazmat.backends import default_backend API = "http://127.0.0.1:8000" LOG_PATH = "/tmp/testrun.log" @@ -48,7 +50,6 @@ BASELINE_MAC_ADDR = "02:42:aa:00:01:01" ALL_MAC_ADDR = "02:42:aa:00:00:01" -TIMESTAMP = "2024-01-01 00:00:00" DEVICE_PROFILE_QUESTIONS = "resources/devices/device_profile.json" def pretty_print(dictionary: dict): @@ -456,7 +457,10 @@ def stop_test(): # Validate system status -def test_start_testrun_success(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_start_testrun_success(empty_devices_dir, add_devices, testrun): # pylint: disable=W0613 """ Test for testrun started successfully (200) """ # Load the device using load_json utility method @@ -526,7 +530,10 @@ def test_start_testrun_invalid_json(testrun): # pylint: disable=W0613 # Check if 'error' in response assert "error" in response -def test_start_testrun_already_started(empty_devices_dir, add_one_device, # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_start_testrun_already_started(empty_devices_dir, add_devices, # pylint: disable=W0613 testrun, start_test): # pylint: disable=W0613 """ Test for testrun already started (409) """ @@ -582,7 +589,10 @@ def test_start_testrun_device_not_found(empty_devices_dir, testrun): # pylint: d # Check if 'error' in response assert "error" in response -def test_start_testrun_error(empty_devices_dir, add_one_device, # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_start_testrun_error(empty_devices_dir, add_devices, # pylint: disable=W0613 update_sys_config, testrun, restore_sys_config): # pylint: disable=W0613 """ Test for start testrun internal server error (500) """ @@ -616,8 +626,11 @@ def test_start_testrun_error(empty_devices_dir, add_one_device, # pylint: disabl # Check if 'error' in response assert "error" in response -def test_stop_running_testrun(empty_devices_dir, add_one_device, # pylint: disable=W0613 - testrun, start_test): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_stop_running_testrun(empty_devices_dir, add_devices, # pylint: disable=W0613 + testrun, start_test): # pylint: disable=W0613 """ Test for successfully stop testrun when test is running (200) """ # Send the post request to stop the test @@ -662,8 +675,11 @@ def test_sys_shutdown(testrun): # pylint: disable=W0613 # Check if null in response assert response is None -def test_sys_shutdown_in_progress(empty_devices_dir, add_one_device, # pylint: disable=W0613 - testrun, start_test): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_sys_shutdown_in_progress(empty_devices_dir, add_devices, # pylint: disable=W0613 + testrun, start_test): # pylint: disable=W0613 """ Test system shutdown during an in-progress test (400) """ # Attempt to shutdown while the test is running @@ -693,8 +709,11 @@ def test_sys_status_idle(testrun): # pylint: disable=W0613 # Check if system status is 'Idle' assert response["status"] == "Idle" -def test_sys_status_cancelled(empty_devices_dir, add_one_device, # pylint: disable=W0613 - testrun, start_test, stop_test): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_sys_status_cancelled(empty_devices_dir, add_devices, # pylint: disable=W0613 + testrun, start_test, stop_test): # pylint: disable=W0613 """ Test for system status 'cancelled' (200) """ # Send the get request to retrieve system status @@ -706,8 +725,11 @@ def test_sys_status_cancelled(empty_devices_dir, add_one_device, # pylint: disab # Check if status is 'Cancelled' assert response["status"] == "Cancelled" -def test_sys_status_waiting(empty_devices_dir, add_one_device, # pylint: disable=W0613 - testrun, start_test): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_sys_status_waiting(empty_devices_dir, add_devices, # pylint: disable=W0613 + testrun, start_test): # pylint: disable=W0613 """ Test for system status 'Waiting for Device' (200) """ # Send the get request @@ -768,49 +790,78 @@ def test_get_test_modules(testrun): # pylint: disable=W0613 # Tests for reports endpoints +def get_timestamp(formatted=False): + """ Returns timestamp value from 'started' field from the report + found at 'testing/api/reports/report.json' + By default it will return the raw time format or iso if formatted=True + """ + + # Load the report.json using load_json utility method + report_json = load_json("report.json", directory="testing/api/reports") + + # Assign the timestamp from report.json + timestamp = report_json["started"] + + # If formatted is changed to 'True' + if formatted: + + # Return the iso formatted timestamp + return timestamp.replace(" ", "T") + + # Else return the raw timestamp + return timestamp + @pytest.fixture def create_report_folder(): # pylint: disable=W0613 """ Fixture to create the device reports folder in local/devices """ - def _create_report_folder(device_name, mac_addr, timestamp): + # Load the device using load_json utility method + device = load_json("device_config.json", directory=DEVICE_1_PATH) - # Create the device folder path - main_folder = os.path.join(DEVICES_DIRECTORY, device_name) + # Assign the device mac address + mac_addr = device["mac_addr"] - # Remove the ":" from mac address for the folder structure - mac_addr = mac_addr.replace(":", "") + # Assign the device name + device_name = f'{device["manufacturer"]} {device["model"]}' - # Change the timestamp format for the folder structure - timestamp = timestamp.replace(" ", "T") + # Create the device folder path + main_folder = os.path.join(DEVICES_DIRECTORY, device_name) - # Create the report folder path - report_folder = os.path.join(main_folder, "reports", timestamp, - "test", mac_addr) + # Remove the ":" from mac address for the folder structure + mac_addr = mac_addr.replace(":", "") - # Ensure the report folder exists - os.makedirs(report_folder, exist_ok=True) + # Assign the timestamp from get_timestamp utility method + timestamp = get_timestamp(formatted=True) - # Iterate over the files from 'testing/api/reports' folder - for file in os.listdir(REPORTS_PATH): + # Create the report folder path + report_folder = os.path.join(main_folder, "reports", timestamp, + "test", mac_addr) - # Construct full path of the file from 'testing/api/reports' folder - source_path = os.path.join(REPORTS_PATH, file) + # Ensure the report folder exists + os.makedirs(report_folder, exist_ok=True) - # Construct full path where the file will be copied - target_path = os.path.join(report_folder, file) + # Iterate over the files from 'testing/api/reports' folder + for file in os.listdir(REPORTS_PATH): - # Copy the file - shutil.copy(source_path, target_path) + # Construct full path of the file from 'testing/api/reports' folder + source_path = os.path.join(REPORTS_PATH, file) - return report_folder + # Construct full path where the file will be copied + target_path = os.path.join(report_folder, file) - return _create_report_folder + # Copy the file + shutil.copy(source_path, target_path) -def test_get_reports_no_reports(testrun): # pylint: disable=W0613 +def test_get_reports_no_reports(empty_devices_dir, testrun): # pylint: disable=W0613 """Test get reports when no reports exist""" + # Set the Origin headers to API address + headers = { + "Origin": API + } + # Send a GET request to the /reports endpoint - r = requests.get(f"{API}/reports", timeout=5) + r = requests.get(f"{API}/reports", headers=headers, timeout=5) # Check if the status code is 200 (OK) assert r.status_code == 200 @@ -824,9 +875,60 @@ def test_get_reports_no_reports(testrun): # pylint: disable=W0613 # Check if the response is an empty list assert response == [] -def test_delete_report(empty_devices_dir, add_one_device, # pylint: disable=W0613 - create_report_folder, testrun): # pylint: disable=W0613 - """Test for succesfully delete a report (200)""" +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_get_reports(empty_devices_dir, add_devices, # pylint: disable=W0613 + create_report_folder, testrun): # pylint: disable=W0613 + """ Test for get reports when one report is available (200) """ + + # Set the Origin headers to API address + headers = { + "Origin": API + } + + # Get request to retrieve the generated reports + r = requests.get(f"{API}/reports", headers=headers, timeout=5) + + # Parse the json + response = r.json() + + # Check if status code is 200 (ok) + assert r.status_code == 200 + + # Check if response is a list + assert isinstance(response, list) + + # Check if there is one report + assert len(response) == 1 + + # Assign the report from the response list + report = response[0] + + # Assign the expected report properties + expected_keys = [ + "testrun", + "mac_addr", + "device", + "status", + "started", + "finished", + "tests", + "report" + ] + + # Iterate through the expected_keys + for key in expected_keys: + + # Check if the key exists in the report + assert key in report + +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_delete_report(empty_devices_dir, add_devices, # pylint: disable=W0613 + create_report_folder, testrun): # pylint: disable=W0613 + """ Test for succesfully delete a report (200) """ # Load the device using load_json utility method device = load_json("device_config.json", directory=DEVICE_1_PATH) @@ -837,13 +939,10 @@ def test_delete_report(empty_devices_dir, add_one_device, # pylint: disable=W061 # Assign the device name device_name = f'{device["manufacturer"]} {device["model"]}' - # Create the report directory - report_folder = create_report_folder(device_name, mac_addr, TIMESTAMP) - # Payload delete_data = { "mac_addr": mac_addr, - "timestamp": TIMESTAMP + "timestamp": get_timestamp() } # Send a DELETE request to remove the report @@ -858,12 +957,18 @@ def test_delete_report(empty_devices_dir, add_one_device, # pylint: disable=W061 # Check if "success" in response assert "success" in response - # Check if report folder has been deleted - assert not os.path.exists(report_folder) + # Construct the 'reports' folder path + reports_folder = os.path.join(device_name, "reports") + + # Check if reports folder has been deleted + assert not os.path.exists(reports_folder) -def test_delete_report_no_payload(empty_devices_dir, add_one_device, # pylint: disable=W0613 - create_report_folder, testrun): # pylint: disable=W0613 - """Test delete report bad request when the payload is missing (400)""" +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_delete_report_no_payload(empty_devices_dir, add_devices, # pylint: disable=W0613 + create_report_folder, testrun): # pylint: disable=W0613 + """ Test delete report bad request when the payload is missing (400) """ # Send a DELETE request to remove the report without the payload r = requests.delete(f"{API}/report", timeout=5) @@ -880,21 +985,12 @@ def test_delete_report_no_payload(empty_devices_dir, add_one_device, # pylint: d # Check if the correct error message returned assert "Invalid request received, missing body" in response["error"] -def test_delete_report_invalid_payload(empty_devices_dir, add_one_device, # pylint: disable=W0613 - create_report_folder, testrun): # pylint: disable=W0613 - """Test delete report bad request, mac addr and timestamp are missing (400)""" - - # Load the device using load_json utility method - device = load_json("device_config.json", directory=DEVICE_1_PATH) - - # Assign the device mac address - mac_addr = device["mac_addr"] - - # Assign the device name - device_name = f'{device["manufacturer"]} {device["model"]}' - - # Create the report directory - create_report_folder(device_name, mac_addr, TIMESTAMP) +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_delete_report_invalid_payload(empty_devices_dir, add_devices, # pylint: disable=W0613 + create_report_folder, testrun): # pylint: disable=W0613 + """ Test delete report bad request missing mac addr or timestamp (400) """ # Empty payload delete_data = {} @@ -914,9 +1010,12 @@ def test_delete_report_invalid_payload(empty_devices_dir, add_one_device, # pyli # Check if the correct error message returned assert "Missing mac address or timestamp" in response["error"] -def test_delete_report_invalid_timestamp(empty_devices_dir, add_one_device, # pylint: disable=W0613 - create_report_folder, testrun): # pylint: disable=W0613 - """Test delete report bad request when timestamp format is not valid (400)""" +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_delete_report_invalid_timestamp(empty_devices_dir, add_devices, # pylint: disable=W0613 + create_report_folder, testrun): # pylint: disable=W0613 + """ Test delete report bad request if timestamp format is not valid (400) """ # Load the device using load_json utility method device = load_json("device_config.json", directory=DEVICE_1_PATH) @@ -924,19 +1023,13 @@ def test_delete_report_invalid_timestamp(empty_devices_dir, add_one_device, # py # Assign the device mac address mac_addr = device["mac_addr"] - # Assign the device name - device_name = f'{device["manufacturer"]} {device["model"]}' - # Assign the incorrect timestamp format - timestamp = "2024-01-01 invalid" - - # Create the report.json - create_report_folder(device_name, mac_addr, timestamp) + invalid_timestamp = "2024-01-01 invalid" # Payload delete_data = { "mac_addr": mac_addr, - "timestamp": timestamp + "timestamp": invalid_timestamp } # Send a DELETE request to remove the report @@ -955,12 +1048,12 @@ def test_delete_report_invalid_timestamp(empty_devices_dir, add_one_device, # py assert "Incorrect timestamp format" in response["error"] def test_delete_report_no_device(empty_devices_dir, testrun): # pylint: disable=W0613 - """Test delete report when device does not exist (404)""" + """ Test delete report when device does not exist (404) """ # Payload to be deleted for a non existing device delete_data = { "mac_addr": "00:1e:42:35:73:c4", - "timestamp": TIMESTAMP + "timestamp": get_timestamp() } # Send the delete request to the endpoint @@ -978,7 +1071,10 @@ def test_delete_report_no_device(empty_devices_dir, testrun): # pylint: disable= # Check if the correct error message returned assert "Could not find device" in response["error"] -def test_delete_report_no_report(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_delete_report_no_report(empty_devices_dir, add_devices, testrun): # pylint: disable=W0613 """Test for delete report when report does not exist (404)""" # Load the device using load_json utility method @@ -990,7 +1086,7 @@ def test_delete_report_no_report(empty_devices_dir, add_one_device, testrun): # # Prepare the payload for the DELETE request delete_data = { "mac_addr": mac_addr, - "timestamp": TIMESTAMP + "timestamp": get_timestamp() } # Send the delete request to delete the report @@ -1010,24 +1106,21 @@ def test_delete_report_no_report(empty_devices_dir, add_one_device, testrun): # # Check if the correct error message is returned assert "Report not found" in response["error"] -def test_get_report_success(empty_devices_dir, add_one_device, # pylint: disable=W0613 - create_report_folder, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_get_report_success(empty_devices_dir, add_devices, # pylint: disable=W0613 + create_report_folder, testrun): # pylint: disable=W0613 """Test for successfully get report when report exists (200)""" # Load the device using load_json utility method device = load_json("device_config.json", directory=DEVICE_1_PATH) - # Assign the device mac address - mac_addr = device["mac_addr"] - # Assign the device name device_name = f'{device["manufacturer"]} {device["model"]}' # Assign the timestamp and change the format - timestamp = TIMESTAMP.replace(" ", "T") - - # Create the report for the device - create_report_folder(device_name, mac_addr, timestamp) + timestamp = get_timestamp(formatted=True) # Send the get request r = requests.get(f"{API}/report/{device_name}/{timestamp}", timeout=5) @@ -1038,7 +1131,10 @@ def test_get_report_success(empty_devices_dir, add_one_device, # pylint: disable # Check if the response is a PDF assert r.headers["Content-Type"] == "application/pdf" -def test_get_report_not_found(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_get_report_not_found(empty_devices_dir, add_devices, testrun): # pylint: disable=W0613 """Test get report when report doesn't exist (404)""" # Load the device using load_json utility method @@ -1047,8 +1143,11 @@ def test_get_report_not_found(empty_devices_dir, add_one_device, testrun): # pyl # Assign the device name device_name = f'{device["manufacturer"]} {device["model"]}' + # Assign the timestamp + timestamp = get_timestamp() + # Send the get request - r = requests.get(f"{API}/report/{device_name}/{TIMESTAMP}", timeout=5) + r = requests.get(f"{API}/report/{device_name}/{timestamp}", timeout=5) # Check if status code is 404 (not found) assert r.status_code == 404 @@ -1065,11 +1164,14 @@ def test_get_report_not_found(empty_devices_dir, add_one_device, testrun): # pyl def test_get_report_device_not_found(empty_devices_dir, testrun): # pylint: disable=W0613 """Test getting a report when the device is not found (404)""" - # Assign device name and timestamp + # Assign device name device_name = "nonexistent_device" + # Assign the timestamp + timestamp = get_timestamp() + # Send the get request - r = requests.get(f"{API}/report/{device_name}/{TIMESTAMP}", timeout=5) + r = requests.get(f"{API}/report/{device_name}/{timestamp}", timeout=5) # Check if is 404 (not found) assert r.status_code == 404 @@ -1083,19 +1185,18 @@ def test_get_report_device_not_found(empty_devices_dir, testrun): # pylint: disa # Check if the correct error message is returned assert "Device not found" in response["error"] -def test_export_report_device_not_found(empty_devices_dir, testrun, # pylint: disable=W0613 - create_report_folder): +def test_export_report_device_not_found(empty_devices_dir, create_report_folder, # pylint: disable=W0613 + testrun): # pylint: disable=W0613 """Test for export the report result when the device could not be found""" - # Assign the non-existing device name, mac_addr + # Assign the non-existing device name device_name = "non existing device" - mac_addr = "00:1e:42:35:73:c4" - # Create the report for the non-existing device - create_report_folder(device_name, mac_addr, TIMESTAMP) + # Assign the timestamp + timestamp = get_timestamp() # Send the post request - r = requests.post(f"{API}/export/{device_name}/{TIMESTAMP}", timeout=5) + r = requests.post(f"{API}/export/{device_name}/{timestamp}", timeout=5) # Check if is 404 (not found) assert r.status_code == 404 @@ -1109,27 +1210,27 @@ def test_export_report_device_not_found(empty_devices_dir, testrun, # pylint: di # Check if the correct error message returned assert "A device with that name could not be found" in response["error"] -def test_export_report_profile_not_found(empty_devices_dir, add_one_device, # pylint: disable=W0613 - create_report_folder, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_export_report_profile_not_found(empty_devices_dir, add_devices, # pylint: disable=W0613 + create_report_folder, testrun): # pylint: disable=W0613 """Test for export report result when the profile is not found""" # Load the device using load_json utility method device = load_json("device_config.json", directory=DEVICE_1_PATH) - # Assign the device mac address - mac_addr = device["mac_addr"] - # Assign the device name device_name = f'{device["manufacturer"]} {device["model"]}' - # Create the report for the device - create_report_folder(device_name, mac_addr, TIMESTAMP) + # Assign the timestamp + timestamp = get_timestamp() # Add a non existing profile into the payload payload = {"profile": "non_existent_profile"} # Send the post request - r = requests.post(f"{API}/export/{device_name}/{TIMESTAMP}", + r = requests.post(f"{API}/export/{device_name}/{timestamp}", json=payload, timeout=5) @@ -1145,7 +1246,10 @@ def test_export_report_profile_not_found(empty_devices_dir, add_one_device, # py # Check if the correct error message returned assert "A profile with that name could not be found" in response["error"] -def test_export_report_not_found(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_export_report_not_found(empty_devices_dir, add_devices, testrun): # pylint: disable=W0613 """Test for export the report result when the report could not be found""" # Load the device using load_json utility method @@ -1154,8 +1258,11 @@ def test_export_report_not_found(empty_devices_dir, add_one_device, testrun): # # Assign the device name device_name = f'{device["manufacturer"]} {device["model"]}' + # Assign the timestamp + timestamp = get_timestamp() + # Send the post request to trigger the zipping process - r = requests.post(f"{API}/export/{device_name}/{TIMESTAMP}", timeout=10) + r = requests.post(f"{API}/export/{device_name}/{timestamp}", timeout=10) # Check if status code is 500 (Internal Server Error) assert r.status_code == 404 @@ -1169,9 +1276,12 @@ def test_export_report_not_found(empty_devices_dir, add_one_device, testrun): # # Check if the correct error message is returned assert "Report could not be found" in response["error"] -def test_export_report_with_profile(empty_devices_dir, add_one_device, # pylint: disable=W0613 - empty_profiles_dir, add_one_profile, # pylint: disable=W0613 - create_report_folder, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices, add_profiles", [ + (["device_1"], ["valid_profile.json"]) +], indirect=True) +def test_export_report_with_profile(empty_devices_dir, add_devices, # pylint: disable=W0613 + empty_profiles_dir, add_profiles, # pylint: disable=W0613 + create_report_folder, testrun): # pylint: disable=W0613 """Test export results with existing profile when report exists (200)""" # Load the profile using load_json utility method @@ -1180,17 +1290,11 @@ def test_export_report_with_profile(empty_devices_dir, add_one_device, # pylint: # Load the device using load_json utility method device = load_json("device_config.json", directory=DEVICE_1_PATH) - # Assign the device mac address - mac_addr = device["mac_addr"] - # Assign the device name device_name = f'{device["manufacturer"]} {device["model"]}' # Assign the timestamp and change the format - timestamp = TIMESTAMP.replace(" ", "T") - - # Create the report for the device - create_report_folder(device_name, mac_addr, timestamp) + timestamp = get_timestamp(formatted=True) # Send the post request r = requests.post(f"{API}/export/{device_name}/{timestamp}", @@ -1203,8 +1307,11 @@ def test_export_report_with_profile(empty_devices_dir, add_one_device, # pylint: # Check if the response is a zip file assert r.headers["Content-Type"] == "application/zip" -def test_export_results_with_no_profile(empty_devices_dir, add_one_device, # pylint: disable=W0613 - create_report_folder, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_export_results_with_no_profile(empty_devices_dir, add_devices, # pylint: disable=W0613 + create_report_folder, testrun): # pylint: disable=W0613 """Test export results with no profile when report exists (200)""" # Load the device using load_json utility method @@ -1213,14 +1320,8 @@ def test_export_results_with_no_profile(empty_devices_dir, add_one_device, # pyl # Assign the device name device_name = f'{device["manufacturer"]} {device["model"]}' - # Assign the device mac address - mac_addr = device["mac_addr"] - # Assign the timestamp and change the format - timestamp = TIMESTAMP.replace(" ", "T") - - # Create the report for the device - create_report_folder(device_name, mac_addr, timestamp) + timestamp = get_timestamp(formatted=True) # Send the post request r = requests.post(f"{API}/export/{device_name}/{timestamp}", timeout=5) @@ -1232,59 +1333,40 @@ def test_export_results_with_no_profile(empty_devices_dir, add_one_device, # pyl assert r.headers["Content-Type"] == "application/zip" # Tests for device endpoints - @pytest.fixture() -def add_one_device(): - """Fixture to create one device during tests""" +def add_devices(request): + """ Upload specified device to local/devices """ - # Load the device configurations using load_json utility method - device = load_json("device_config.json", directory=DEVICE_1_PATH) + # Access the parameter (devices list) provided to the fixture + devices = request.param - # Assign the device name - device_name = f'{device["manufacturer"]} {device["model"]}' - - # Construct full path of the device from 'testing/api/devices/device_1' - source_path = os.path.join(DEVICE_1_PATH, "device_config.json") - - # Construct full path where the device will be copied - target_path = os.path.join(DEVICES_DIRECTORY, device_name) - - # Create the target directory if it doesn't exist - os.makedirs(target_path, exist_ok=True) - - # Copy device_config from 'testing/api/devices/device_1' to 'local/devices' - shutil.copy(source_path, target_path) - -@pytest.fixture() -def add_two_devices(): - """Fixture to create two devices during tests""" - - # List of device folders from 'testing/api/devices' - devices = ["device_1", "device_2"] - - for file in devices: + # Iterate over the device names provided + for device in devices: # Construct the full path for the device_config.json - device_path = os.path.join(DEVICES_PATH, file) + device_path = os.path.join(DEVICES_PATH, device) # Load the device configurations using load_json utility method device = load_json("device_config.json", directory=device_path) - # Assign the device name - device_name = f'{device["manufacturer"]} {device["model"]}' + # Assign the device name for the target directory + target_device_name = f'{device["manufacturer"]} {device["model"]}' # Construct the source path of the device config file source_path = os.path.join(device_path, "device_config.json") - # Construct the target path where the profile will be copied - target_path = os.path.join(DEVICES_DIRECTORY, device_name) + # Construct the target path where the device config will be copied + target_path = os.path.join(DEVICES_DIRECTORY, target_device_name) # Create the target directory if it doesn't exist os.makedirs(target_path, exist_ok=True) - # Copy the profile from source to target + # Copy the device config from source to target shutil.copy(source_path, target_path) + # Return the list with devices names + return devices + def delete_all_devices(): """Utility method to delete all devices from local/devices""" @@ -1333,7 +1415,7 @@ def empty_devices_dir(): delete_all_devices() def get_all_devices(): - """ Returns list with paths to all devices from local/devices""" + """ Returns list with paths to all devices from local/devices """ # List to store the paths of all 'device_config.json' files devices = [] @@ -1360,7 +1442,7 @@ def get_all_devices(): return devices def device_exists(device_mac): - """Utility method to check if device exists""" + """ Utility method to check if device exists """ # Send the get request r = requests.get(f"{API}/devices", timeout=5) @@ -1375,12 +1457,17 @@ def device_exists(device_mac): # Return if mac address is in the list of devices return any(p["mac_addr"] == device_mac for p in devices) -def test_get_devices_no_devices(empty_devices_dir, testrun): # pylint: disable=W0613 - """ Test for get devices endpoint when no devices are available (200) """ - - # Error handling if there are devices in local/devices - if len(get_all_devices()) != 0: - raise Exception("Expected no devices in local/devices") +@pytest.mark.parametrize( + "add_devices", + [ + [], + ["device_1"], + ["device_1", "device_2"] + ], + indirect=True +) +def test_get_devices(empty_devices_dir, add_devices, testrun): # pylint: disable=W0613 + """ Test get devices when none, one or two devices are available (200) """ # Send the get request to retrieve all devices r = requests.get(f"{API}/devices", timeout=5) @@ -1394,51 +1481,10 @@ def test_get_devices_no_devices(empty_devices_dir, testrun): # pylint: disable=W # Check if response is a list assert isinstance(response, list) - # Check if the list is empty - assert len(response) == 0 + # Check if the number of devices matches the number of devices available + assert len(response) == len(add_devices) - # Check if there are no devices in local/devices - assert len(get_all_devices()) == 0 - -def test_get_devices_one_device(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613 - """ Test for get devices endpoint when one device is created (200) """ - - # Error handling if there is not one device in local/devices - if len(get_all_devices()) != 1: - raise Exception("Expected one device in local/devices") - - # Send get request to the "/devices" endpoint - r = requests.get(f"{API}/devices", timeout=5) - - # Check if status code is 200 (OK) - assert r.status_code == 200 - - # Parse the json response (devices) - response = r.json() - - # Check if response contains one device - assert len(response) == 1 - -def test_get_devices_two_devices(empty_devices_dir, add_two_devices, testrun): # pylint: disable=W0613 - """ Test for get devices endpoint when two devices are created (200) """ - - # Error handling if there are not two devices in local/devices - if len(get_all_devices()) != 2: - raise Exception("Expected two devices in local/devices") - - # Send get request to the "/devices" endpoint - r = requests.get(f"{API}/devices", timeout=5) - - # Check if status code is 200 (OK) - assert r.status_code == 200 - - # Parse the response (devices) - response = r.json() - - # Check if response contains one device - assert len(response) == 2 - - # Assign the expected fields from device + # Assign the expected device fields expected_fields = [ "status", "mac_addr", @@ -1450,11 +1496,14 @@ def test_get_devices_two_devices(empty_devices_dir, add_two_devices, testrun): # "test_modules", ] - # Iterate over all expected_fields list - for field in expected_fields: + # If devices are in the list + if len(add_devices) > 0: + + # Iterate over all expected_fields list + for field in expected_fields: - # Check if both devices have the expected fields - assert all(field in r for r in response) + # Check if devices have the expected fields + assert all(field in device for device in response) def test_create_device(empty_devices_dir, testrun): # pylint: disable=W0613 """ Test for successfully create device endpoint (201) """ @@ -1504,7 +1553,10 @@ def test_create_device(empty_devices_dir, testrun): # pylint: disable=W0613 # Check if both devices have been found assert len(created_devices) == 2 -def test_create_device_already_exists(empty_devices_dir, add_one_device, # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_create_device_already_exists(empty_devices_dir, add_devices, # pylint: disable=W0613 testrun): # pylint: disable=W0613 """ Test for crete device when device already exists (409) """ @@ -1573,8 +1625,11 @@ def test_create_device_invalid_request(empty_devices_dir, testrun): # pylint: di # Check if 'local/device' has no devices assert len(get_all_devices()) == 0 -def test_edit_device(empty_devices_dir, add_one_device, # pylint: disable=W0613 - testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_edit_device(empty_devices_dir, add_devices, # pylint: disable=W0613 + testrun): # pylint: disable=W0613 """ Test for successfully edit device (200) """ # Error handling if there is not one devices in local/devices @@ -1704,8 +1759,15 @@ def test_edit_device_invalid_json(empty_devices_dir, testrun): # pylint: disable # Check if 'error' in response assert "error" in response -def test_edit_device_mac_already_exists( empty_devices_dir, add_two_devices, # pylint: disable=W0613 - testrun): # pylint: disable=W0613 +@pytest.mark.parametrize( + "add_devices", + [ + ["device_1", "device_2"] + ], + indirect=True +) +def test_edit_device_mac_already_exists( empty_devices_dir, add_devices, # pylint: disable=W0613 + testrun): # pylint: disable=W0613 """ Test for edit device when the mac address already exists (409) """ # Load the first device (payload) using load_json utility method @@ -1744,8 +1806,11 @@ def test_edit_device_mac_already_exists( empty_devices_dir, add_two_devices, # p # Check if 'error' in response assert "error" in response -def test_edit_device_test_in_progress(empty_devices_dir, add_one_device, # pylint: disable=W0613 - testrun, start_test): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_edit_device_test_in_progress(empty_devices_dir, add_devices, # pylint: disable=W0613 + testrun, start_test): # pylint: disable=W0613 """ Test for edit device when a test is in progress (403) """ # Load the device (payload) using load_json utility method @@ -1803,8 +1868,11 @@ def test_edit_device_test_in_progress(empty_devices_dir, add_one_device, # pyli # Check that device "manufacturer" was not updated assert device["model"] != updated_device["model"] -def test_edit_device_invalid_manufacturer(empty_devices_dir, add_one_device, # pylint: disable=W0613 - testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_edit_device_invalid_manufacturer(empty_devices_dir, add_devices, # pylint: disable=W0613 + testrun): # pylint: disable=W0613 """ Test for edit device invalid chars in 'manufacturer' field (400) """ # Load the device @@ -1826,7 +1894,10 @@ def test_edit_device_invalid_manufacturer(empty_devices_dir, add_one_device, # p # Check if 'error' in response assert "error" in response -def test_edit_device_invalid_model(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_edit_device_invalid_model(empty_devices_dir, add_devices, testrun): # pylint: disable=W0613 """ Test for edit device invalid chars in 'model' field (400) """ # Load the device @@ -1870,7 +1941,10 @@ def test_edit_long_chars(empty_devices_dir, testrun): # pylint: disable=W0613 # Check if 'error' in response assert "error" in response -def test_delete_device(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_delete_device(empty_devices_dir, add_devices, testrun): # pylint: disable=W0613 """ Test for succesfully delete device endpoint (200) """ # Load the device @@ -1935,7 +2009,10 @@ def test_delete_device_not_found(empty_devices_dir, testrun): # pylint: disable= # Check if error in response assert "error" in response -def test_delete_device_no_mac(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_delete_device_no_mac(empty_devices_dir, add_devices, testrun): # pylint: disable=W0613 """ Test for delete device when no mac address in payload (400) """ # Assign an empty payload (no mac address) @@ -1958,8 +2035,11 @@ def test_delete_device_no_mac(empty_devices_dir, add_one_device, testrun): # pyl # Check that device wasn't deleted from 'local/devices' assert len(get_all_devices()) == 1 -def test_delete_device_testrun_in_progress(empty_devices_dir, add_one_device, # pylint: disable=W0613 - testrun, start_test): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_delete_device_testrun_in_progress(empty_devices_dir, add_devices, # pylint: disable=W0613 + testrun, start_test): # pylint: disable=W0613 """ Test for delete device when testrun is in progress (403) """ # Load the device details @@ -2157,7 +2237,7 @@ def delete_all_certs(): # System related issues print(f"Error removing {item}: {err}") -def load_certificate_file(cert_filename): +def load_cert_file(cert_filename): """ Utility method to load a certificate file in binary read mode """ # Construct the full file path @@ -2169,86 +2249,59 @@ def load_certificate_file(cert_filename): # Return the certificate file return cert_file.read() -@pytest.fixture() -def reset_certs(): - """ Delete the certificates before and after each test """ - - # Delete before the test - delete_all_certs() +def extract_name(cert_data): + """ Utility method to extract the Common Name (CN) from cert data """ - yield + # Load the cert using the cryptography library + cert = x509.load_pem_x509_certificate(cert_data, default_backend()) - # Delete after the test - delete_all_certs() + # Extract and return the common name value + return cert.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)[0].value @pytest.fixture() -def add_cert(): - """ Upload certificates during tests """ - - # Utility method to upload a certificate - def _upload_cert(filename): +def add_certs(request): + """ Upload specified certificates to local/root_certs """ - # Load the certificate using the utility method - cert_file = load_certificate_file(filename) + # Access the parameter (certs list) provided to the fixture + certs = request.param - # Send a POST request to the API endpoint to upload the certificate - response = requests.post( - f"{API}/system/config/certs", - files={"file": (filename, cert_file, "application/x-x509-ca-cert")}, - timeout=5) + # Iterate over the certificate names provided + for cert in certs: - # Return the response - return response - - # Returning the reference to upload_certificate - return _upload_cert - -def test_get_certs_no_certs(reset_certs, testrun): # pylint: disable=W0613 - """ Test for get certificate when no certificates are available (200) """ - - # Send the get request to "/system/config/certs" endpoint - r = requests.get(f"{API}/system/config/certs", timeout=5) + # Construct the full path for cert from 'testing/api/certificates' + source_path = os.path.join(CERTS_PATH, cert) - # Check if status code is 200 (OK) - assert r.status_code == 200 - - # Parse the response (certificates) - response = r.json() - - # Check if response is a list - assert isinstance(response, list) + # Copy the cert from 'testing/api/certificates' to 'local/root_certs' + shutil.copy(source_path, CERTS_DIRECTORY) - # Check if the list is empty - assert len(response) == 0 + # Return the list with certs name + return certs -def test_get_certs(testrun, reset_certs, add_cert): # pylint: disable=W0613 - """ Test for get certificates (one and two certificates) (200) """ - - # Use add_cert fixture to upload the first certificate - add_cert("crt.pem") - - # Send the get request to "/system/config/certs" endpoint - r = requests.get(f"{API}/system/config/certs", timeout=5) - - # Check if status code is 200 (OK) - assert r.status_code == 200 +@pytest.fixture() +def reset_certs(): + """ Delete the certificates before and after each test """ - # Parse the response (certificates) - response = r.json() + # Delete before the test + delete_all_certs() - # Check if response is a list - assert isinstance(response, list) + yield - # Check if response contains one certificate - assert len(response) == 1 + # Delete after the test + delete_all_certs() - # Use add_cert fixture to upload the second certificate - add_cert("WR2.pem") +# Use parametrize to create a test suite for 3 scenarios +@pytest.mark.parametrize("add_certs", [ + [], + ["crt.pem"], + ["crt.pem", "WR2.pem"], +], indirect=True) +def test_get_certs(reset_certs, add_certs, testrun): # pylint: disable=W0613 + """ Test for get certs when none, one or two certs are available (200) """ - # Send the get request to "/system/config/certs" endpoint + # Send the GET request to "/system/config/certs" endpoint r = requests.get(f"{API}/system/config/certs", timeout=5) - # Check if status code is 200 (OK) + # Check if the status code is 200 (OK) assert r.status_code == 200 # Parse the response (certificates) @@ -2257,14 +2310,14 @@ def test_get_certs(testrun, reset_certs, add_cert): # pylint: disable=W0613 # Check if response is a list assert isinstance(response, list) - # Check if response contains two certificates - assert len(response) == 2 + # Check if the number of certs matches the number of certs available + assert len(response) == len(add_certs) -def test_upload_cert(testrun, reset_certs): # pylint: disable=W0613 +def test_upload_cert(reset_certs, testrun): # pylint: disable=W0613 """ Test for upload certificate successfully (200) """ # Load the first certificate file content using the utility method - cert_file = load_certificate_file("crt.pem") + cert_file = load_cert_file("crt.pem") # Send a POST request to the API endpoint to upload the certificate r = requests.post( @@ -2286,7 +2339,7 @@ def test_upload_cert(testrun, reset_certs): # pylint: disable=W0613 assert response["filename"] == "crt.pem" # Load the second certificate file using the utility method - cert_file = load_certificate_file("WR2.pem") + cert_file = load_cert_file("WR2.pem") # Send a POST request to the API endpoint to upload the second certificate r = requests.post( @@ -2319,11 +2372,11 @@ def test_upload_cert(testrun, reset_certs): # pylint: disable=W0613 # Check if "WR2.pem" exists assert any(cert["filename"] == "WR2.pem" for cert in response) -def test_upload_invalid_cert_format(testrun, reset_certs): # pylint: disable=W0613 +def test_upload_invalid_cert_format(reset_certs, testrun): # pylint: disable=W0613 """ Test for upload an invalid certificate format (400) """ # Load the first certificate file content using the utility method - cert_file = load_certificate_file("invalid.pem") + cert_file = load_cert_file("invalid.pem") # Send a POST request to the API endpoint to upload the certificate r = requests.post( @@ -2341,14 +2394,14 @@ def test_upload_invalid_cert_format(testrun, reset_certs): # pylint: disable=W06 # Check if "error" key is in response assert "error" in response -def test_upload_invalid_cert_name(testrun, reset_certs): # pylint: disable=W0613 +def test_upload_invalid_cert_name(reset_certs, testrun): # pylint: disable=W0613 """ Test for upload a valid certificate with invalid filename (400) """ # Assign the invalid certificate name to a variable cert_name = "invalidname1234567891234.pem" # Load the first certificate file content using the utility method - cert_file = load_certificate_file(cert_name) + cert_file = load_cert_file(cert_name) # Send a POST request to the API endpoint to upload the certificate r = requests.post( @@ -2366,33 +2419,12 @@ def test_upload_invalid_cert_name(testrun, reset_certs): # pylint: disable=W0613 # Check if "error" key is in response assert "error" in response -def test_upload_existing_cert(testrun, reset_certs): # pylint: disable=W0613 +@pytest.mark.parametrize("add_certs", [["crt.pem"]], indirect=True) +def test_upload_existing_cert(reset_certs, add_certs, testrun): # pylint: disable=W0613 """ Test for upload an existing certificate (409) """ - # Load the first certificate file content using the utility method - cert_file = load_certificate_file("crt.pem") - - # Send a POST request to the API endpoint to upload the certificate - r = requests.post( - f"{API}/system/config/certs", - files={"file": ("crt.pem", cert_file, "application/x-x509-ca-cert")}, - timeout=5 - ) - - # Check if status code is 201 (Created) - assert r.status_code == 201 - - # Parse the response - response = r.json() - - # Check if 'filename' field is in the response - assert "filename" in response - - # Check if the certificate name is 'crt.pem' - assert response["filename"] == "crt.pem" - - # Load the same certificate file content using the utility method - cert_file = load_certificate_file("crt.pem") + # Load the cert file content using the utility method + cert_file = load_cert_file("crt.pem") # Send a POST request to the API endpoint to upload the second certificate r = requests.post( @@ -2410,28 +2442,46 @@ def test_upload_existing_cert(testrun, reset_certs): # pylint: disable=W0613 # Check if "error" key is in response assert "error" in response -def test_delete_cert(testrun, reset_certs, add_cert): # pylint: disable=W0613 +@pytest.mark.parametrize("add_certs", [["crt.pem", "WR2.pem"]], indirect=True) +def test_delete_cert_success(reset_certs, add_certs, testrun): # pylint: disable=W0613 """ Test for successfully deleting an existing certificate (200) """ - # Use the add_cert fixture to upload the first certificate - add_cert("crt.pem") + # Load the first cert details to extract the 'name' value + uploaded_cert = load_cert_file("crt.pem") + + # Assign the 'name' value from certificate + cert_name = extract_name(uploaded_cert) + + # Assign the payload + delete_payload = {"name": cert_name} + + # Send delete certificate request + r = requests.delete(f"{API}/system/config/certs", + data=json.dumps(delete_payload), + timeout=5) + + # Check if status code is 200 (OK) + assert r.status_code == 200 - # Retrieve the uploaded certificate's details + # Send the get request to display all certificates r = requests.get(f"{API}/system/config/certs", timeout=5) # Parse the json response response = r.json() - # Extract the name of the uploaded certificate - uploaded_cert = next( - (cert for cert in response if cert["filename"] == "crt.pem") - ) + # Check that the certificate is no longer listed + assert not any(cert["filename"] == "crt.pem" for cert in response) - # Assign the certificate name - cert_name = uploaded_cert["name"] + # Load the second cert details to extract the 'name' value + uploaded_cert = load_cert_file("WR2.pem") - # Send delete certificate request + # Assign the 'name' value from certificate + cert_name = extract_name(uploaded_cert) + + # Assign the payload delete_payload = {"name": cert_name} + + # Send delete certificate request r = requests.delete(f"{API}/system/config/certs", data=json.dumps(delete_payload), timeout=5) @@ -2446,14 +2496,12 @@ def test_delete_cert(testrun, reset_certs, add_cert): # pylint: disable=W0613 response = r.json() # Check that the certificate is no longer listed - assert not any(cert["filename"] == "crt.pem" for cert in response) + assert not any(cert["filename"] == "WR2.pem" for cert in response) -def test_delete_cert_bad_request(testrun, reset_certs, add_cert): # pylint: disable=W0613 +@pytest.mark.parametrize("add_certs", [["crt.pem"]], indirect=True) +def test_delete_cert_bad_request(reset_certs, add_certs, testrun): # pylint: disable=W0613 """ Test for delete a certificate without providing the name (400)""" - # Use the add_cert fixture to upload the certificate - add_cert("crt.pem") - # Empty payload delete_payload = {} @@ -2471,7 +2519,7 @@ def test_delete_cert_bad_request(testrun, reset_certs, add_cert): # pylint: disa # Check if error in response assert "error" in response -def test_delete_cert_not_found(testrun, reset_certs): # pylint: disable=W0613 +def test_delete_cert_not_found(reset_certs, testrun): # pylint: disable=W0613 """ Test for delete certificate when does not exist (404) """ # Attempt to delete a certificate with a name that doesn't exist @@ -2494,21 +2542,14 @@ def test_delete_cert_not_found(testrun, reset_certs): # pylint: disable=W0613 # Tests for profile endpoints @pytest.fixture() -def add_one_profile(): - """ Create one profile during tests """ - - # Construct full path of the profile from 'testing/api/profiles' folder - source_path = os.path.join(PROFILES_PATH, "valid_profile.json") +def add_profiles(request): + """ Upload specified profile to local/risk_profiles """ - # Copy the profile from 'testing/api/profiles' to 'local/risk_profiles' - shutil.copy(source_path, PROFILES_DIRECTORY) - -@pytest.fixture() -def add_two_profiles(): - """ Create two profiles during tests """ + # Access the parameter (profiles list) provided to the fixture + profiles = request.param - # Iterate over the files from 'testing/api/profiles' folder - for profile in os.listdir(PROFILES_PATH): + # Iterate over the profile names provided + for profile in profiles: # Construct full path of the file from 'testing/api/profiles' folder source_path = os.path.join(PROFILES_PATH, profile) @@ -2516,6 +2557,9 @@ def add_two_profiles(): # Copy the file_name from 'testing/api/profiles' to 'local/risk_profiles' shutil.copy(source_path, PROFILES_DIRECTORY) + # Return the list with profiles name + return profiles + def delete_all_profiles(): """Utility method to delete all profiles from local/risk_profiles""" @@ -2579,6 +2623,32 @@ def profile_exists(profile_name): # Return if name is in the list of profiles return any(p["name"] == profile_name for p in profiles) +@pytest.fixture() +def remove_risk_assessment(): + """ Fixture to remove and restore risk_assessment.json """ + + # Path to the risk_assessment.json file + risk_assessment_path = os.path.join("resources", "risk_assessment.json") + + # Backup path for the risk_assessment.json file + backup_path = os.path.join("resources", "risk_assessment_backup.json") + + # Create a backup of the risk_assessment.json file + if os.path.exists(risk_assessment_path): + shutil.copy(risk_assessment_path, backup_path) + + # Delete the risk_assessment.json file + if os.path.exists(risk_assessment_path): + os.remove(risk_assessment_path) + + # Run the test + yield + + # Restore the risk assessment file after the test + if os.path.exists(backup_path): + shutil.copy(backup_path, risk_assessment_path) + os.remove(backup_path) + def test_get_profiles_format(testrun): # pylint: disable=W0613 """ Test for profiles format (200) """ @@ -2599,31 +2669,19 @@ def test_get_profiles_format(testrun): # pylint: disable=W0613 assert "question" in item assert "type" in item -def test_get_profiles_no_profiles(empty_profiles_dir, testrun): # pylint: disable=W0613 - """ Test for get profiles when no profiles created (200) """ - - # Send the get request to "/profiles" endpoint - r = requests.get(f"{API}/profiles", timeout=5) - - # Check if status code is 200 (OK) - assert r.status_code == 200 - - # Parse the response (profiles) - response = r.json() - - # Check if response is a list - assert isinstance(response, list) - - # Check if the list is empty - assert len(response) == 0 - -def test_get_profiles_one_profile(empty_profiles_dir, add_one_profile, testrun): # pylint: disable=W0613 - """ Test for get profiles when one profile is created (200) """ +# Use parametrize to create a test suite for 3 scenarios +@pytest.mark.parametrize("add_profiles", [ + [], + ["valid_profile.json"], + ["valid_profile.json", "draft_profile.json"], +], indirect=True) +def test_get_profiles(empty_profiles_dir, add_profiles, testrun): # pylint: disable=W0613 + """ Test get profiles when none, one or two profiles are available (200) """ # Send get request to the "/profiles" endpoint r = requests.get(f"{API}/profiles", timeout=5) - # Check if status code is 200 (OK) + # Check if the status code is 200 (OK) assert r.status_code == 200 # Parse the response (profiles) @@ -2632,50 +2690,43 @@ def test_get_profiles_one_profile(empty_profiles_dir, add_one_profile, testrun): # Check if response is a list assert isinstance(response, list) - # Check if response contains one profile - assert len(response) == 1 - - # Check that each profile has the expected fields - for profile in response: - for field in ["name", "status", "created", "version", "questions", "risk"]: - assert field in profile - - # Assign profile["questions"] - profile_questions = profile["questions"] + # Check if the number of profiles matches the number of profiles available + assert len(response) == len(add_profiles) - # Check if "questions" value is a list - assert isinstance(profile_questions, list) + # Assign the expected profile fields + expected_fields = [ + "name", "status", "created", "version", "questions", "risk" + ] - # Check that "questions" value has the expected fields - for element in profile_questions: + # Check if profile exist + if len(add_profiles) > 0: - # Check if each element is dict - assert isinstance(element, dict) + # Iterate through profiles + for profile in response: - # Check if "question" key is in dict element - assert "question" in element + # Iterate through expected_fields list + for field in expected_fields: - # Check if "asnswer" key is in dict element - assert "answer" in element + # Check if the field is in profile + assert field in profile -def test_get_profiles_two_profiles(empty_profiles_dir, add_two_profiles, # pylint: disable=W0613 - testrun): # pylint: disable=W0613 - """ Test for get profiles when two profiles are created (200) """ + # Assign profile["questions"] + profile_questions = profile["questions"] - # Send the get request to "/profiles" endpoint - r = requests.get(f"{API}/profiles", timeout=5) + # Check if "questions" value is a list + assert isinstance(profile_questions, list) - # Parse the response (profiles) - response = r.json() + # Check that "questions" value has the expected fields + for element in profile_questions: - # Check if status code is 200 (OK) - assert r.status_code == 200 + # Check if each element is dict + assert isinstance(element, dict) - # Check if response is a list - assert isinstance(response, list) + # Check if "question" key is in dict element + assert "question" in element - # Check if response contains two profiles - assert len(response) == 2 + # Check if "asnswer" key is in dict element + assert "answer" in element def test_create_profile(testrun): # pylint: disable=W0613 """ Test for create profile when profile does not exist (201) """ @@ -2719,7 +2770,10 @@ def test_create_profile(testrun): # pylint: disable=W0613 # Check if profile was created assert created_profile is not None -def test_update_profile(empty_profiles_dir, add_one_profile, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_profiles", [ + ["valid_profile.json"] +], indirect=True) +def test_update_profile(empty_profiles_dir, add_profiles, testrun): # pylint: disable=W0613 """ Test for update profile when profile already exists (200) """ # Load the profile using load_json utility method @@ -2777,7 +2831,34 @@ def test_update_profile(empty_profiles_dir, add_one_profile, testrun): # pylint: # Check if profile was updated assert updated_profile_check is not None -def test_update_profile_invalid_json(empty_profiles_dir, add_one_profile, # pylint: disable=W0613 +def test_update_profile_no_profiles_format(empty_profiles_dir, # pylint: disable=W0613 + remove_risk_assessment, testrun): # pylint: disable=W0613 + """Test for profile update when profiles format is not available (501)""" + + # Prepare a valid profile update request + profile_update = load_json("valid_profile.json", directory=PROFILES_PATH) + + # Send a POST request to update the profile + r = requests.post(f"{API}/profiles", + data=json.dumps(profile_update), + timeout=5) + + # Check if the response status code is 501 (Not Implemented) + assert r.status_code == 501 + + # Parse the response + response = r.json() + + # Check if "error" key is present in the response + assert "error" in response + + # Check if the error message matches the expected response + assert response["error"] == "Risk profiles are not available right now" + +@pytest.mark.parametrize("add_profiles", [ + ["valid_profile.json"] +], indirect=True) +def test_update_profile_invalid_json(empty_profiles_dir, add_profiles, # pylint: disable=W0613 testrun): # pylint: disable=W0613 """ Test for update profile invalid JSON payload (400) """ @@ -2820,7 +2901,10 @@ def test_create_profile_invalid_json(empty_profiles_dir, testrun): # pylint: dis # Check if "error" key in response assert "error" in response -def test_delete_profile(empty_profiles_dir, add_one_profile, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_profiles", [ + ["valid_profile.json"] +], indirect=True) +def test_delete_profile(empty_profiles_dir, add_profiles, testrun): # pylint: disable=W0613 """ Test for successfully delete profile (200) """ # Load the profile using load_json utility method @@ -2922,7 +3006,10 @@ def test_delete_profile_invalid_json(empty_profiles_dir, testrun): # pylint: dis # Check if "error" key in response assert "error" in response -def test_delete_profile_server_error(empty_profiles_dir, add_one_profile, # pylint: disable=W0613 +@pytest.mark.parametrize("add_profiles", [ + ["valid_profile.json"] +], indirect=True) +def test_delete_profile_server_error(empty_profiles_dir, add_profiles, # pylint: disable=W0613 testrun): # pylint: disable=W0613 """ Test for delete profile causing internal server error (500) """ @@ -2994,7 +3081,10 @@ def test_delete_device_testrun_running(testing_devices, testrun): # pylint: disa assert r.status_code == 403 @pytest.mark.skip() -def test_stop_running_test(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_stop_running_test(empty_devices_dir, add_devices, testrun): # pylint: disable=W0613 """ Test for successfully stop testrun when test is running (200) """ # Load the device and mac address using add_device utility method diff --git a/testing/baseline/test_baseline b/testing/baseline/test_baseline index 4ab0d75e8..44a17d348 100755 --- a/testing/baseline/test_baseline +++ b/testing/baseline/test_baseline @@ -40,7 +40,7 @@ sudo cp testing/baseline/system.json local/system.json # Copy device configs to testrun sudo cp -r testing/device_configs/* local/devices -sudo bin/testrun --single-intf --no-ui --validate > $TESTRUN_OUT 2>&1 & +sudo bin/testrun --single-intf --no-ui --target 02:42:aa:00:01:01 -fw 1.0 --validate > $TESTRUN_OUT 2>&1 & TPID=$! # Time to wait for testrun to be ready diff --git a/testing/device_configs/tester1/device_config.json b/testing/device_configs/tester1/device_config.json index f9eeccb6a..0c94febd2 100644 --- a/testing/device_configs/tester1/device_config.json +++ b/testing/device_configs/tester1/device_config.json @@ -2,6 +2,9 @@ "manufacturer": "Google", "model": "Tester 1", "mac_addr": "02:42:aa:00:00:01", + "type": "Controller - FCU", + "technology": "Hardware - Fitness", + "test_pack": "Device Qualification", "test_modules": { "dns": { "enabled": true @@ -24,5 +27,31 @@ "tls": { "enabled": false } - } + }, + "additional_info": [ + { + "question": "What type of device is this?", + "answer": "Controller - FCU" + }, + { + "question": "Please select the technology this device falls into", + "answer": "Hardware - Fitness" + }, + { + "question": "Does your device process any sensitive information? ", + "answer": "No" + }, + { + "question": "Can all non-essential services be disabled on your device?", + "answer": "Yes" + }, + { + "question": "Is there a second IP port on the device?", + "answer": "No" + }, + { + "question": "Can the second IP port on your device be disabled?", + "answer": "N/A" + } + ] } diff --git a/testing/device_configs/tester2/device_config.json b/testing/device_configs/tester2/device_config.json index ccbc14585..d816cb2ef 100644 --- a/testing/device_configs/tester2/device_config.json +++ b/testing/device_configs/tester2/device_config.json @@ -2,6 +2,9 @@ "manufacturer": "Google", "model": "Tester 2", "mac_addr": "02:42:aa:00:00:02", + "type": "Controller - FCU", + "technology": "Hardware - Fitness", + "test_pack": "Device Qualification", "test_modules": { "dns": { "enabled": true @@ -24,5 +27,31 @@ "tls": { "enabled": false } - } + }, + "additional_info": [ + { + "question": "What type of device is this?", + "answer": "Controller - FCU" + }, + { + "question": "Please select the technology this device falls into", + "answer": "Hardware - Fitness" + }, + { + "question": "Does your device process any sensitive information? ", + "answer": "No" + }, + { + "question": "Can all non-essential services be disabled on your device?", + "answer": "Yes" + }, + { + "question": "Is there a second IP port on the device?", + "answer": "No" + }, + { + "question": "Can the second IP port on your device be disabled?", + "answer": "N/A" + } + ] } diff --git a/testing/device_configs/tester3/device_config.json b/testing/device_configs/tester3/device_config.json index b7792027e..7b0206ca3 100644 --- a/testing/device_configs/tester3/device_config.json +++ b/testing/device_configs/tester3/device_config.json @@ -2,6 +2,9 @@ "manufacturer": "Google", "model": "Tester 3", "mac_addr": "02:42:aa:00:00:03", + "type": "Controller - FCU", + "technology": "Hardware - Fitness", + "test_pack": "Device Qualification", "test_modules": { "dns": { "enabled": false @@ -18,5 +21,31 @@ "nmap": { "enabled": false } - } + }, + "additional_info": [ + { + "question": "What type of device is this?", + "answer": "Controller - FCU" + }, + { + "question": "Please select the technology this device falls into", + "answer": "Hardware - Fitness" + }, + { + "question": "Does your device process any sensitive information? ", + "answer": "No" + }, + { + "question": "Can all non-essential services be disabled on your device?", + "answer": "Yes" + }, + { + "question": "Is there a second IP port on the device?", + "answer": "No" + }, + { + "question": "Can the second IP port on your device be disabled?", + "answer": "N/A" + } + ] } diff --git a/testing/unit/conn/conn_module_test.py b/testing/unit/conn/conn_module_test.py index 0687fc1a6..1513545f0 100644 --- a/testing/unit/conn/conn_module_test.py +++ b/testing/unit/conn/conn_module_test.py @@ -133,14 +133,42 @@ def connection_port_speed_autonegotiation_fail_test(self): def connection_switch_dhcp_snooping_icmp_test(self): LOGGER.info('connection_switch_dhcp_snooping_icmp_test') conn_module = ConnectionModule(module=MODULE, - log_dir=OUTPUT_DIR, - results_dir=OUTPUT_DIR, - startup_capture_file=STARTUP_CAPTURE_FILE, - monitor_capture_file=MONITOR_CAPTURE_FILE) - result = conn_module._connection_switch_dhcp_snooping() # pylint: disable=W0212 + results_dir=OUTPUT_DIR, + startup_capture_file=STARTUP_CAPTURE_FILE, + monitor_capture_file=MONITOR_CAPTURE_FILE) + result = conn_module._connection_switch_dhcp_snooping() # pylint: disable=W0212 LOGGER.info(result) self.assertEqual(result[0], True) + def communication_network_type_test(self): + LOGGER.info('communication_network_type_test') + conn_module = ConnectionModule(module=MODULE, + results_dir=OUTPUT_DIR, + startup_capture_file=STARTUP_CAPTURE_FILE, + monitor_capture_file=MONITOR_CAPTURE_FILE) + result = conn_module._communication_network_type() # pylint: disable=W0212 + details_expected = { + 'mac_address': '98:f0:7b:d1:87:06', + 'multicast': { + 'from': 11, + 'to': 0 + }, + 'broadcast': { + 'from': 13, + 'to': 0 + }, + 'unicast': { + 'from': 0, + 'to': 0 + } + } + LOGGER.info(result) + self.assertEqual(result[0], 'Informational') + self.assertEqual(result[1], 'Packet types detected: Multicast, Broadcast') + self.assertEqual(result[2], details_expected) + #self.assertEqual(result[0], True) + + if __name__ == '__main__': suite = unittest.TestSuite() @@ -163,6 +191,9 @@ def connection_switch_dhcp_snooping_icmp_test(self): suite.addTest( ConnectionModuleTest('connection_switch_dhcp_snooping_icmp_test')) + # DHCP Snooping related tests + suite.addTest(ConnectionModuleTest('communication_network_type_test')) + runner = unittest.TextTestRunner() test_result = runner.run(suite) diff --git a/testing/unit/dns/dns_module_test.py b/testing/unit/dns/dns_module_test.py index 5c100cbf9..d530498dd 100644 --- a/testing/unit/dns/dns_module_test.py +++ b/testing/unit/dns/dns_module_test.py @@ -49,7 +49,6 @@ def setUpClass(cls): # Test the module report generation def dns_module_report_test(self): dns_module = DNSModule(module=MODULE, - log_dir=OUTPUT_DIR, results_dir=OUTPUT_DIR, dns_server_capture_file=DNS_SERVER_CAPTURE_FILE, startup_capture_file=STARTUP_CAPTURE_FILE, @@ -98,7 +97,6 @@ def dns_module_report_no_dns_test(self): wrpcap(monitor_cap_file, packets_monitor) dns_module = DNSModule(module='dns', - log_dir=OUTPUT_DIR, results_dir=OUTPUT_DIR, dns_server_capture_file=dns_server_cap_file, startup_capture_file=startup_cap_file, diff --git a/testing/unit/ntp/ntp_module_test.py b/testing/unit/ntp/ntp_module_test.py index af74722f1..52bd32aa9 100644 --- a/testing/unit/ntp/ntp_module_test.py +++ b/testing/unit/ntp/ntp_module_test.py @@ -16,6 +16,7 @@ import unittest from scapy.all import rdpcap, NTP, wrpcap import os +import shutil import sys MODULE = 'ntp' @@ -47,7 +48,6 @@ def setUpClass(cls): # Test the module report generation def ntp_module_report_test(self): ntp_module = NTPModule(module=MODULE, - log_dir=OUTPUT_DIR, results_dir=OUTPUT_DIR, ntp_server_capture_file=NTP_SERVER_CAPTURE_FILE, startup_capture_file=STARTUP_CAPTURE_FILE, @@ -63,6 +63,11 @@ def ntp_module_report_test(self): with open(LOCAL_REPORT, 'r', encoding='utf-8') as file: report_local = file.read() + # Copy the generated html report to a new file + new_report_name = 'ntp_local.html' + new_report_path = os.path.join(OUTPUT_DIR, new_report_name) + shutil.copy(report_out_path, new_report_path) + self.assertEqual(report_out, report_local) # Test the module report generation if no DNS traffic @@ -96,7 +101,6 @@ def ntp_module_report_no_ntp_test(self): wrpcap(monitor_cap_file, packets_monitor) ntp_module = NTPModule(module='dns', - log_dir=OUTPUT_DIR, results_dir=OUTPUT_DIR, ntp_server_capture_file=ntp_server_cap_file, startup_capture_file=startup_cap_file, @@ -112,6 +116,11 @@ def ntp_module_report_no_ntp_test(self): with open(LOCAL_REPORT_NO_NTP, 'r', encoding='utf-8') as file: report_local = file.read() + # Copy the generated html report to a new file + new_report_name = 'ntp_no_ntp.html' + new_report_path = os.path.join(OUTPUT_DIR, new_report_name) + shutil.copy(report_out_path, new_report_path) + self.assertEqual(report_out, report_local) if __name__ == '__main__': diff --git a/testing/unit/ntp/reports/ntp_report_local.html b/testing/unit/ntp/reports/ntp_report_local.html index 025881db5..1fe5e3f3a 100644 --- a/testing/unit/ntp/reports/ntp_report_local.html +++ b/testing/unit/ntp/reports/ntp_report_local.html @@ -18,7 +18,7 @@

NTP Module

- +
diff --git a/testing/unit/report/report_compliant.json b/testing/unit/report/report_compliant.json index 17e994d20..6b84a8d39 100644 --- a/testing/unit/report/report_compliant.json +++ b/testing/unit/report/report_compliant.json @@ -4,6 +4,9 @@ "manufacturer": "Testrun", "model": "Faux", "firmware": "1.0.0", + "type": "Controller - FCU", + "technology": "Hardware - Fitness", + "test_pack": "Device Qualification", "test_modules": { "connection": { "enabled": true @@ -23,7 +26,33 @@ "protocol": { "enabled": true } - } + }, + "additional_info": [ + { + "question": "What type of device is this?", + "answer": "Controller - FCU" + }, + { + "question": "Please select the technology this device falls into", + "answer": "Hardware - Fitness" + }, + { + "question": "Does your device process any sensitive information? ", + "answer": "No" + }, + { + "question": "Can all non-essential services be disabled on your device?", + "answer": "Yes" + }, + { + "question": "Is there a second IP port on the device?", + "answer": "No" + }, + { + "question": "Can the second IP port on your device be disabled?", + "answer": "N/A" + } + ] }, "status": "Compliant", "started": "2024-04-10 21:21:47", @@ -68,7 +97,7 @@ }, { "name": "connection.switch.arp_inspection", - "description": "Device uses ARP", + "description": "Device uses ARP correctly", "expected_behavior": "Device continues to operate correctly when ARP inspection is enabled on the switch. No functionality is lost with ARP inspection enabled.", "required_result": "Required", "result": "Compliant" diff --git a/testing/unit/report/report_noncompliant.json b/testing/unit/report/report_noncompliant.json index 98fbeb284..6619bba19 100644 --- a/testing/unit/report/report_noncompliant.json +++ b/testing/unit/report/report_noncompliant.json @@ -4,6 +4,9 @@ "manufacturer": "Testrun", "model": "Faux", "firmware": "1.0.0", + "type": "Controller - FCU", + "technology": "Hardware - Fitness", + "test_pack": "Device Qualification", "test_modules": { "connection": { "enabled": true @@ -23,7 +26,33 @@ "protocol": { "enabled": true } - } + }, + "additional_info": [ + { + "question": "What type of device is this?", + "answer": "Controller - FCU" + }, + { + "question": "Please select the technology this device falls into", + "answer": "Hardware - Fitness" + }, + { + "question": "Does your device process any sensitive information? ", + "answer": "No" + }, + { + "question": "Can all non-essential services be disabled on your device?", + "answer": "Yes" + }, + { + "question": "Is there a second IP port on the device?", + "answer": "No" + }, + { + "question": "Can the second IP port on your device be disabled?", + "answer": "N/A" + } + ] }, "status": "Non-Compliant", "started": "2024-04-10 21:21:47", @@ -77,7 +106,7 @@ }, { "name": "connection.switch.arp_inspection", - "description": "Device uses ARP", + "description": "Device uses ARP correctly", "expected_behavior": "Device continues to operate correctly when ARP inspection is enabled on the switch. No functionality is lost with ARP inspection enabled.", "required_result": "Required", "result": "Compliant" diff --git a/testing/unit/report/report_test.py b/testing/unit/report/report_test.py index e5c8b61a5..f706059b6 100644 --- a/testing/unit/report/report_test.py +++ b/testing/unit/report/report_test.py @@ -61,6 +61,7 @@ def create_report(self, results_file_path): # Load each module html report reports_md = [] + reports_md.append(self.get_module_html_report('tls')) reports_md.append(self.get_module_html_report('dns')) reports_md.append(self.get_module_html_report('services')) reports_md.append(self.get_module_html_report('ntp')) @@ -70,12 +71,16 @@ def create_report(self, results_file_path): # Create the HTML filename based on the JSON name file_name = os.path.splitext(os.path.basename(results_file_path))[0] - report_out_file = os.path.join(OUTPUT_DIR, file_name + '.html') + report_html_file = os.path.join(OUTPUT_DIR, file_name + '.html') + report_pdf_file = os.path.join(OUTPUT_DIR, file_name + '.pdf') # Save report as HTML file - with open(report_out_file, 'w', encoding='utf-8') as file: + with open(report_html_file, 'w', encoding='utf-8') as file: file.write(report.to_html()) + with open(report_pdf_file, 'wb') as file: + file.write(report.to_pdf().getvalue()) + def report_compliant_test(self): """Generate a report for the compliant test""" diff --git a/testing/unit/run_test_module.sh b/testing/unit/run_test_module.sh old mode 100644 new mode 100755 index 37516ee72..8e31e6860 --- a/testing/unit/run_test_module.sh +++ b/testing/unit/run_test_module.sh @@ -15,6 +15,10 @@ # limitations under the License. # Must be run from the root directory of Testrun + +# Read the JSON file into a variable +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
01(T!bpGu1zaVm7AY%TB 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𝔐KWjnwTmBT8ELi5&SCSlM-G2iH0t63GN0fZIis)S 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

2J7d0Wc6byu%OgMoVm>;)`emB?#(qa#jST~ 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 literal 0 HcmV?d00001 diff --git a/testing/unit/tls/captures/multi_page_startup.pcap b/testing/unit/tls/captures/multi_page_startup.pcap new file mode 100644 index 0000000000000000000000000000000000000000..e6089ee2ba16fb803fe9e65254590bc29a9b4a8c GIT binary patch literal 11129 zcmds-3v3is6o&7e-R-v9vMnJ2#HNCxK%up;Ezk#0Xu+yQ%Cku2QA$A|lt*bQph#-% z5-ll&1Qq0!U<3jtf+oD$pnyCR5J>8&%HCVXAbPxC=fGYn%2?NcI223!*EqO`p^ zA@+F5@$fK0!%3K#Q2G0ori3cIazbo2n@jHUDfmo)oEH>OBTrb)LGQZkG@Ig(p7xbt zcBrCv_v}!eHZVI4=#Zi~idM&e8flbs>m#jN4Xj?^tXdPn>b2J4MpjLav}!W2TF+TE zy$DuUt(V7}Zp@h}#aNDEv(B7eQd~N{ zG@`6Dp}4SgenQWP!dZn2XO|R~mzs)Z&%%b(2UH&kbykb&v$TG`Kd4w4Rddo64|(+I z;vv^QDIT&H;2lkZc;wfcjTFSjn!DQdW6iC;qSdN5!bk;xaOY+$rpsKn5vzYwl!n9G?-)v%m16J*LHdk067FJ)gx z8RhIT=8v8bp^_gABgB;8o-@4?_?&$KSNvEx!z&`0x*1KG!J!Vdx|>A$dY$`e6&Z9o zlaO(8{P2=8FTpWRQgL|hVN7}5KxIC{?`(~L^xG9SsUvolHB2t$PNkcaD%jbjVc}A) z*aW}msvsAS0qxPan}#bdS0L%5jXFQu$~8$Lar51b39(`m%&Lzjh0g?%(J1)GjShB@ z9-fvKeMn&HIhW1Xfc_$xT zrZzjhU$7|e<;Gs%_r*Fne@YJHYOGTIXR8Zu6lm`xjnzCwiLTX7vzI@8dQ=orUdS*R zRV<6HVp*V0DODL~8SJ^MvxScW=-#g=Kzk=Cs^zaQJU@BTH*nrg9qam+#O*N`t3$@}Mrbc#XQo7O(3^;yStguH0$;tN+g!)yBsycMwb$?K>PDa%3%fXIaU!eT4#meiTcvz8sMtyju(E8I1LYayy5dHbMI; zvN=C9fYlMwJLHCkz{d}+dsJaJNgDN9X#}7`bjS-TUP4{fq5@E18lW;7f*rebpp-4u zRHGoMFaszvpV!pyAW-@NCH`2jDgA-+V~U_^S~sF_FmkhmQkMn&h;ovgi`Z0up4D%p zj1wX~Jb_qw7=^ZX2ou`2zp%1e=8TB+r`$nh^#OH6LJiiUUa7mY&mR<2)&bzv-j%_Q zT_#W#-U&8kAW(J`>(*W&yYEE$mvecogP59qt%8)-4(fai**)hVpl|KclJv^5U`IF$ zd~}wFf)dltV>LM{4e@O45D%c5phI3z$0Ssi78O7>$pO@c3{PIIXoslU`wVq14Qu3@ z2g}p_DO^w^eL%IBP>ovDsdqB>_=AEPnG0SWn`2VnYv|^eAg|bl0{zG^k1>5pa`c|o z(EwuV1FD0Bx~oM65K}~Tf8cuWB;PqI&-49!?}WCT4L-D-79;f%ItwW_8^n)7ATpX1cpY%6D6IK89?@KzjVzmYOm0fSx`f*byEMl)IBd zK}pJTJ&V&-AzZpP!zy4a?)N@+YqFXn|`h$XbVkDs2!AmXnOe{DV8U>W02@zx>TpjM3H}>!*ln(EDmq6_D zwxT5PSCUP#XukuODGejbOKzU+)QG=mH@WI)&;6gj6;+HZN};q*E4siG78M=a$gT;Ojv-+;9{<8hZZT2Oa@HdU59_G*dVk97A)k?UL55=R#|I z$!I58B%3Dhs}po7#^10Fwtq5HEcC`V<0c2bB8#JDl)DMf&@kzZc`Sb7AF|vxjPigq z!zc@GQ5L{Vv>fGHqbNti?@|Vzb`sKp2R5kLaEwQs7Y3|ucgbaaADO#ZHu1=T6?-}Htgy7?+x}b!2IMM zJmm4SYVEF7tCo^!j05aXaMiK|T(vODiysI+{T<6SZZM**Z|n$dowpeN{m6%0|7qOQ Q%iNX^%@~ literal 0 HcmV?d00001 diff --git a/testing/unit/tls/captures/multi_page_tls.pcap b/testing/unit/tls/captures/multi_page_tls.pcap new file mode 100644 index 0000000000000000000000000000000000000000..fc76ebe99579a935f81c128d90f418088cb567be GIT binary patch literal 235069 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 literal 0 HcmV?d00001 diff --git a/testing/unit/tls/certs/_.google.com.crt b/testing/unit/tls/certs/_.google.com.crt index 442bf6ec3..99bb16833 100644 --- a/testing/unit/tls/certs/_.google.com.crt +++ b/testing/unit/tls/certs/_.google.com.crt @@ -1,77 +1,77 @@ ------BEGIN CERTIFICATE----- -MIIN4zCCDMugAwIBAgIRAMX7uoXXmJy3CoNgnp7ELOMwDQYJKoZIhvcNAQELBQAw -OzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFUdvb2dsZSBUcnVzdCBTZXJ2aWNlczEM -MAoGA1UEAxMDV1IyMB4XDTI0MDcwMTA2MzU0M1oXDTI0MDkyMzA2MzU0MlowFzEV -MBMGA1UEAwwMKi5nb29nbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE -1mu/GyluisEPwCtCgrJeDhhGpQ9pvMzgMsbtBi3cFm0+bGTyNyoTRDSTdUsLRJNJ -BRF2O/M8i6CCi6PrYenOhKOCC88wggvLMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUE -DDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTQ+KGTW7bq4kmI -AhTEY+bMVA8+mDAfBgNVHSMEGDAWgBTeGx7teRXUPjckwyG77DQ5bUKyMDBYBggr -BgEFBQcBAQRMMEowIQYIKwYBBQUHMAGGFWh0dHA6Ly9vLnBraS5nb29nL3dyMjAl -BggrBgEFBQcwAoYZaHR0cDovL2kucGtpLmdvb2cvd3IyLmNydDCCCaUGA1UdEQSC -CZwwggmYggwqLmdvb2dsZS5jb22CFiouYXBwZW5naW5lLmdvb2dsZS5jb22CCSou -YmRuLmRldoIVKi5vcmlnaW4tdGVzdC5iZG4uZGV2ghIqLmNsb3VkLmdvb2dsZS5j -b22CGCouY3Jvd2Rzb3VyY2UuZ29vZ2xlLmNvbYIYKi5kYXRhY29tcHV0ZS5nb29n -bGUuY29tggsqLmdvb2dsZS5jYYILKi5nb29nbGUuY2yCDiouZ29vZ2xlLmNvLmlu -gg4qLmdvb2dsZS5jby5qcIIOKi5nb29nbGUuY28udWuCDyouZ29vZ2xlLmNvbS5h -coIPKi5nb29nbGUuY29tLmF1gg8qLmdvb2dsZS5jb20uYnKCDyouZ29vZ2xlLmNv -bS5jb4IPKi5nb29nbGUuY29tLm14gg8qLmdvb2dsZS5jb20udHKCDyouZ29vZ2xl -LmNvbS52boILKi5nb29nbGUuZGWCCyouZ29vZ2xlLmVzggsqLmdvb2dsZS5mcoIL -Ki5nb29nbGUuaHWCCyouZ29vZ2xlLml0ggsqLmdvb2dsZS5ubIILKi5nb29nbGUu -cGyCCyouZ29vZ2xlLnB0gg8qLmdvb2dsZWFwaXMuY26CESouZ29vZ2xldmlkZW8u -Y29tggwqLmdzdGF0aWMuY26CECouZ3N0YXRpYy1jbi5jb22CD2dvb2dsZWNuYXBw -cy5jboIRKi5nb29nbGVjbmFwcHMuY26CEWdvb2dsZWFwcHMtY24uY29tghMqLmdv -b2dsZWFwcHMtY24uY29tggxna2VjbmFwcHMuY26CDiouZ2tlY25hcHBzLmNughJn -b29nbGVkb3dubG9hZHMuY26CFCouZ29vZ2xlZG93bmxvYWRzLmNughByZWNhcHRj -aGEubmV0LmNughIqLnJlY2FwdGNoYS5uZXQuY26CEHJlY2FwdGNoYS1jbi5uZXSC -EioucmVjYXB0Y2hhLWNuLm5ldIILd2lkZXZpbmUuY26CDSoud2lkZXZpbmUuY26C -EWFtcHByb2plY3Qub3JnLmNughMqLmFtcHByb2plY3Qub3JnLmNughFhbXBwcm9q -ZWN0Lm5ldC5jboITKi5hbXBwcm9qZWN0Lm5ldC5jboIXZ29vZ2xlLWFuYWx5dGlj -cy1jbi5jb22CGSouZ29vZ2xlLWFuYWx5dGljcy1jbi5jb22CF2dvb2dsZWFkc2Vy -dmljZXMtY24uY29tghkqLmdvb2dsZWFkc2VydmljZXMtY24uY29tghFnb29nbGV2 -YWRzLWNuLmNvbYITKi5nb29nbGV2YWRzLWNuLmNvbYIRZ29vZ2xlYXBpcy1jbi5j -b22CEyouZ29vZ2xlYXBpcy1jbi5jb22CFWdvb2dsZW9wdGltaXplLWNuLmNvbYIX -Ki5nb29nbGVvcHRpbWl6ZS1jbi5jb22CEmRvdWJsZWNsaWNrLWNuLm5ldIIUKi5k -b3VibGVjbGljay1jbi5uZXSCGCouZmxzLmRvdWJsZWNsaWNrLWNuLm5ldIIWKi5n -LmRvdWJsZWNsaWNrLWNuLm5ldIIOZG91YmxlY2xpY2suY26CECouZG91YmxlY2xp -Y2suY26CFCouZmxzLmRvdWJsZWNsaWNrLmNughIqLmcuZG91YmxlY2xpY2suY26C -EWRhcnRzZWFyY2gtY24ubmV0ghMqLmRhcnRzZWFyY2gtY24ubmV0gh1nb29nbGV0 -cmF2ZWxhZHNlcnZpY2VzLWNuLmNvbYIfKi5nb29nbGV0cmF2ZWxhZHNlcnZpY2Vz -LWNuLmNvbYIYZ29vZ2xldGFnc2VydmljZXMtY24uY29tghoqLmdvb2dsZXRhZ3Nl -cnZpY2VzLWNuLmNvbYIXZ29vZ2xldGFnbWFuYWdlci1jbi5jb22CGSouZ29vZ2xl -dGFnbWFuYWdlci1jbi5jb22CGGdvb2dsZXN5bmRpY2F0aW9uLWNuLmNvbYIaKi5n -b29nbGVzeW5kaWNhdGlvbi1jbi5jb22CJCouc2FmZWZyYW1lLmdvb2dsZXN5bmRp -Y2F0aW9uLWNuLmNvbYIWYXBwLW1lYXN1cmVtZW50LWNuLmNvbYIYKi5hcHAtbWVh -c3VyZW1lbnQtY24uY29tggtndnQxLWNuLmNvbYINKi5ndnQxLWNuLmNvbYILZ3Z0 -Mi1jbi5jb22CDSouZ3Z0Mi1jbi5jb22CCzJtZG4tY24ubmV0gg0qLjJtZG4tY24u -bmV0ghRnb29nbGVmbGlnaHRzLWNuLm5ldIIWKi5nb29nbGVmbGlnaHRzLWNuLm5l -dIIMYWRtb2ItY24uY29tgg4qLmFkbW9iLWNuLmNvbYIUZ29vZ2xlc2FuZGJveC1j -bi5jb22CFiouZ29vZ2xlc2FuZGJveC1jbi5jb22CHiouc2FmZW51cC5nb29nbGVz -YW5kYm94LWNuLmNvbYINKi5nc3RhdGljLmNvbYIUKi5tZXRyaWMuZ3N0YXRpYy5j -b22CCiouZ3Z0MS5jb22CESouZ2NwY2RuLmd2dDEuY29tggoqLmd2dDIuY29tgg4q -LmdjcC5ndnQyLmNvbYIQKi51cmwuZ29vZ2xlLmNvbYIWKi55b3V0dWJlLW5vY29v -a2llLmNvbYILKi55dGltZy5jb22CC2FuZHJvaWQuY29tgg0qLmFuZHJvaWQuY29t -ghMqLmZsYXNoLmFuZHJvaWQuY29tggRnLmNuggYqLmcuY26CBGcuY2+CBiouZy5j -b4IGZ29vLmdsggp3d3cuZ29vLmdsghRnb29nbGUtYW5hbHl0aWNzLmNvbYIWKi5n -b29nbGUtYW5hbHl0aWNzLmNvbYIKZ29vZ2xlLmNvbYISZ29vZ2xlY29tbWVyY2Uu -Y29tghQqLmdvb2dsZWNvbW1lcmNlLmNvbYIIZ2dwaHQuY26CCiouZ2dwaHQuY26C -CnVyY2hpbi5jb22CDCoudXJjaGluLmNvbYIIeW91dHUuYmWCC3lvdXR1YmUuY29t -gg0qLnlvdXR1YmUuY29tghR5b3V0dWJlZWR1Y2F0aW9uLmNvbYIWKi55b3V0dWJl -ZWR1Y2F0aW9uLmNvbYIPeW91dHViZWtpZHMuY29tghEqLnlvdXR1YmVraWRzLmNv -bYIFeXQuYmWCByoueXQuYmWCGmFuZHJvaWQuY2xpZW50cy5nb29nbGUuY29tghMq -LmFuZHJvaWQuZ29vZ2xlLmNughIqLmNocm9tZS5nb29nbGUuY26CFiouZGV2ZWxv -cGVycy5nb29nbGUuY24wEwYDVR0gBAwwCjAIBgZngQwBAgEwNgYDVR0fBC8wLTAr -oCmgJ4YlaHR0cDovL2MucGtpLmdvb2cvd3IyL0dTeVQxTjRQQnJnLmNybDCCAQQG -CisGAQQB1nkCBAIEgfUEgfIA8AB1ANq2v2s/tbYin5vCu1xr6HCRcWy7UYSFNL2k -PTBI1/urAAABkG04oVMAAAQDAEYwRAIgAaPd62uAnl5mGI2sgPrRH/vRicAYxwaQ -hj9qnla8Z2ACIBwm4r6O2jnYSzMfFYoxaWa7Zn+7bgbUb1uUrzEe4xl7AHcA7s3Q -ZNXbGs7FXLedtM0TojKHRny87N7DUUhZRnEftZsAAAGQbTihHQAABAMASDBGAiEA -7+ZDT9MHQdLS4XEbXqbGAkbp7GQIYbSdoZg/DALMIm8CIQD3cwXxry/8R2I9BQXA -7SM0047FardcOzEV40z+kMDJ4jANBgkqhkiG9w0BAQsFAAOCAQEAd5GYYcv4ZG6P -OiQgV8RVO0TFSBfYUwNQDyeFQFyaagPvP3LbsJC5m9X22jdf64HpIcyKr7Aw4JIY -RzhqvlIZ2t8Hf1McRavJzCl4hER1dLNlzhCL1z+Wq/xiRz5VqVOXv1okG5YgGiEW -UQCBXPFM8nTAror5sMk1e/2IQOI4/4A2MhWZBrpVwYyaKEJUcdKLq9EbDRHVfsNo -0BQCnt/thD6IzL3FYWsEF0636Trxd9chE2r3JF30ORJm8q90Xz2aWwrFGlB0i8tW -uYv+WTi3qW9o2lLaqfsSTjoOmtGxANknI/0LARuBST3xSGk5GD+r/r8J/6LcCe8x -qvx81n6dcQ== ------END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIOCjCCDPKgAwIBAgIQQagVgnQLepAJtGHnFyDA9DANBgkqhkiG9w0BAQsFADA7 +MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZpY2VzMQww +CgYDVQQDEwNXUjIwHhcNMjQxMDIxMDgzNjU3WhcNMjUwMTEzMDgzNjU2WjAXMRUw +EwYDVQQDDAwqLmdvb2dsZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARD +7W/bU6abojd0puaRMYsiXqZjXddRl8yW2qlTpw+HOHg3bA183UxWTtZx+yeHSVQE +3k0jMGvR7C4B3FY+n92ao4IL9zCCC/MwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQM +MAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFIz1r4xlXzoNaxGY +xiGq0aGZuvJpMB8GA1UdIwQYMBaAFN4bHu15FdQ+NyTDIbvsNDltQrIwMFgGCCsG +AQUFBwEBBEwwSjAhBggrBgEFBQcwAYYVaHR0cDovL28ucGtpLmdvb2cvd3IyMCUG +CCsGAQUFBzAChhlodHRwOi8vaS5wa2kuZ29vZy93cjIuY3J0MIIJzQYDVR0RBIIJ +xDCCCcCCDCouZ29vZ2xlLmNvbYIWKi5hcHBlbmdpbmUuZ29vZ2xlLmNvbYIJKi5i +ZG4uZGV2ghUqLm9yaWdpbi10ZXN0LmJkbi5kZXaCEiouY2xvdWQuZ29vZ2xlLmNv +bYIYKi5jcm93ZHNvdXJjZS5nb29nbGUuY29tghgqLmRhdGFjb21wdXRlLmdvb2ds +ZS5jb22CCyouZ29vZ2xlLmNhggsqLmdvb2dsZS5jbIIOKi5nb29nbGUuY28uaW6C +DiouZ29vZ2xlLmNvLmpwgg4qLmdvb2dsZS5jby51a4IPKi5nb29nbGUuY29tLmFy +gg8qLmdvb2dsZS5jb20uYXWCDyouZ29vZ2xlLmNvbS5icoIPKi5nb29nbGUuY29t +LmNvgg8qLmdvb2dsZS5jb20ubXiCDyouZ29vZ2xlLmNvbS50coIPKi5nb29nbGUu +Y29tLnZuggsqLmdvb2dsZS5kZYILKi5nb29nbGUuZXOCCyouZ29vZ2xlLmZyggsq +Lmdvb2dsZS5odYILKi5nb29nbGUuaXSCCyouZ29vZ2xlLm5sggsqLmdvb2dsZS5w +bIILKi5nb29nbGUucHSCDyouZ29vZ2xlYXBpcy5jboIRKi5nb29nbGV2aWRlby5j +b22CDCouZ3N0YXRpYy5jboIQKi5nc3RhdGljLWNuLmNvbYIPZ29vZ2xlY25hcHBz +LmNughEqLmdvb2dsZWNuYXBwcy5jboIRZ29vZ2xlYXBwcy1jbi5jb22CEyouZ29v +Z2xlYXBwcy1jbi5jb22CDGdrZWNuYXBwcy5jboIOKi5na2VjbmFwcHMuY26CEmdv +b2dsZWRvd25sb2Fkcy5jboIUKi5nb29nbGVkb3dubG9hZHMuY26CEHJlY2FwdGNo +YS5uZXQuY26CEioucmVjYXB0Y2hhLm5ldC5jboIQcmVjYXB0Y2hhLWNuLm5ldIIS +Ki5yZWNhcHRjaGEtY24ubmV0ggt3aWRldmluZS5jboINKi53aWRldmluZS5jboIR +YW1wcHJvamVjdC5vcmcuY26CEyouYW1wcHJvamVjdC5vcmcuY26CEWFtcHByb2pl +Y3QubmV0LmNughMqLmFtcHByb2plY3QubmV0LmNughdnb29nbGUtYW5hbHl0aWNz +LWNuLmNvbYIZKi5nb29nbGUtYW5hbHl0aWNzLWNuLmNvbYIXZ29vZ2xlYWRzZXJ2 +aWNlcy1jbi5jb22CGSouZ29vZ2xlYWRzZXJ2aWNlcy1jbi5jb22CEWdvb2dsZXZh +ZHMtY24uY29tghMqLmdvb2dsZXZhZHMtY24uY29tghFnb29nbGVhcGlzLWNuLmNv +bYITKi5nb29nbGVhcGlzLWNuLmNvbYIVZ29vZ2xlb3B0aW1pemUtY24uY29tghcq +Lmdvb2dsZW9wdGltaXplLWNuLmNvbYISZG91YmxlY2xpY2stY24ubmV0ghQqLmRv +dWJsZWNsaWNrLWNuLm5ldIIYKi5mbHMuZG91YmxlY2xpY2stY24ubmV0ghYqLmcu +ZG91YmxlY2xpY2stY24ubmV0gg5kb3VibGVjbGljay5jboIQKi5kb3VibGVjbGlj +ay5jboIUKi5mbHMuZG91YmxlY2xpY2suY26CEiouZy5kb3VibGVjbGljay5jboIR +ZGFydHNlYXJjaC1jbi5uZXSCEyouZGFydHNlYXJjaC1jbi5uZXSCHWdvb2dsZXRy +YXZlbGFkc2VydmljZXMtY24uY29tgh8qLmdvb2dsZXRyYXZlbGFkc2VydmljZXMt +Y24uY29tghhnb29nbGV0YWdzZXJ2aWNlcy1jbi5jb22CGiouZ29vZ2xldGFnc2Vy +dmljZXMtY24uY29tghdnb29nbGV0YWdtYW5hZ2VyLWNuLmNvbYIZKi5nb29nbGV0 +YWdtYW5hZ2VyLWNuLmNvbYIYZ29vZ2xlc3luZGljYXRpb24tY24uY29tghoqLmdv +b2dsZXN5bmRpY2F0aW9uLWNuLmNvbYIkKi5zYWZlZnJhbWUuZ29vZ2xlc3luZGlj +YXRpb24tY24uY29tghZhcHAtbWVhc3VyZW1lbnQtY24uY29tghgqLmFwcC1tZWFz +dXJlbWVudC1jbi5jb22CC2d2dDEtY24uY29tgg0qLmd2dDEtY24uY29tggtndnQy +LWNuLmNvbYINKi5ndnQyLWNuLmNvbYILMm1kbi1jbi5uZXSCDSouMm1kbi1jbi5u +ZXSCFGdvb2dsZWZsaWdodHMtY24ubmV0ghYqLmdvb2dsZWZsaWdodHMtY24ubmV0 +ggxhZG1vYi1jbi5jb22CDiouYWRtb2ItY24uY29tghRnb29nbGVzYW5kYm94LWNu +LmNvbYIWKi5nb29nbGVzYW5kYm94LWNuLmNvbYIeKi5zYWZlbnVwLmdvb2dsZXNh +bmRib3gtY24uY29tgg0qLmdzdGF0aWMuY29tghQqLm1ldHJpYy5nc3RhdGljLmNv +bYIKKi5ndnQxLmNvbYIRKi5nY3BjZG4uZ3Z0MS5jb22CCiouZ3Z0Mi5jb22CDiou +Z2NwLmd2dDIuY29tghAqLnVybC5nb29nbGUuY29tghYqLnlvdXR1YmUtbm9jb29r +aWUuY29tggsqLnl0aW1nLmNvbYILYW5kcm9pZC5jb22CDSouYW5kcm9pZC5jb22C +EyouZmxhc2guYW5kcm9pZC5jb22CBGcuY26CBiouZy5jboIEZy5jb4IGKi5nLmNv +ggZnb28uZ2yCCnd3dy5nb28uZ2yCFGdvb2dsZS1hbmFseXRpY3MuY29tghYqLmdv +b2dsZS1hbmFseXRpY3MuY29tggpnb29nbGUuY29tghJnb29nbGVjb21tZXJjZS5j +b22CFCouZ29vZ2xlY29tbWVyY2UuY29tgghnZ3BodC5jboIKKi5nZ3BodC5jboIK +dXJjaGluLmNvbYIMKi51cmNoaW4uY29tggh5b3V0dS5iZYILeW91dHViZS5jb22C +DSoueW91dHViZS5jb22CEW11c2ljLnlvdXR1YmUuY29tghMqLm11c2ljLnlvdXR1 +YmUuY29tghR5b3V0dWJlZWR1Y2F0aW9uLmNvbYIWKi55b3V0dWJlZWR1Y2F0aW9u +LmNvbYIPeW91dHViZWtpZHMuY29tghEqLnlvdXR1YmVraWRzLmNvbYIFeXQuYmWC +ByoueXQuYmWCGmFuZHJvaWQuY2xpZW50cy5nb29nbGUuY29tghMqLmFuZHJvaWQu +Z29vZ2xlLmNughIqLmNocm9tZS5nb29nbGUuY26CFiouZGV2ZWxvcGVycy5nb29n +bGUuY24wEwYDVR0gBAwwCjAIBgZngQwBAgEwNgYDVR0fBC8wLTAroCmgJ4YlaHR0 +cDovL2MucGtpLmdvb2cvd3IyL29RNm55cjhGMG0wLmNybDCCAQQGCisGAQQB1nkC +BAIEgfUEgfIA8AB1AH1ZHhLheCp7HGFnfF79+NCHXBSgTpWeuQMv2Q6MLnm4AAAB +kq5v6a4AAAQDAEYwRAIgIqLlTU1VytekLFYe6z7B9QZvvAFEtlvZ/rQAndvx4BcC +IDhSWEOXYUPI6kr3vu50z40WJeAuKG8/FkmCKArIiuRJAHcAzxFW7tUufK/zh1vZ +aS6b6RpxZ0qwF+ysAdJbd87MOwgAAAGSrm/pugAABAMASDBGAiEA8kgns/UoNAL7 +/3WsQF/6ifdMBOunXlADyPxvWbpRPe0CIQCQphqwbXlfq7EkCyUnzvMJmMs9PDoM +v7elVg36zqA5HjANBgkqhkiG9w0BAQsFAAOCAQEAOp1JjUVTIoBGg90DPzx1y/0N +3qu5UVDGbuuPlDFzHmjjnGb20C/s6pJzAuUxMvvIClk6m12PCbS/F6ESGYf9QzIU +BmU5BEiOHVSuTQrnDvUzrspB0eSe2qEqfDDvoaSb7MPX5kO7crHdBtG+DFUzUa/C +ZYsgbFW7or+ennD8oJT6NFCvPah7iIJwx5S1NIBPEot8IwzKBP9VrjVBhp9yXXr1 +hWai/Z2gOacoJsLzbEifB+hlwldNgYwdSnVkTe6n5FZIygGxGm97Dp0v54g0Qs+U +4cfvwPIfx7D8oBz0EiCPlRm89TjTr0y8LqujdfiZdfbQQsUsNgsVNtIiiY0CSw== +-----END CERTIFICATE----- diff --git a/testing/unit/tls/reports/tls_report_ext_local.html b/testing/unit/tls/reports/tls_report_ext_local.html new file mode 100644 index 000000000..df1ebada2 --- /dev/null +++ b/testing/unit/tls/reports/tls_report_ext_local.html @@ -0,0 +1,114 @@ +

TLS Module

+ + +
Source
+ + + + + + + + + + + + + + + + + + + +
ExpiryLengthTypePort numberSigned by
2027-07-25 15:33:09888EC443Sub CA
+ +
+
+
Certificate Information
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
Version3 (0x2)
Signature Alg.sha256WithRSAEncryption
Validity from2022-07-26 15:33:09
Valid to2027-07-25 15:33:09
+ +
+
+
Subject Information
+ + + + + + + + + + + + + + + + + + + + +
PropertyValue
CUS
CNapc27D605.nam.gad.schneider-electric.com
+
+
+ + +
Certificate Extensions
+ + + + + + + + + + + + + + +
PropertyValue
subjectAltNameap9643_qa1941270129.nam.gad.schneider-electric.com
+

Outbound Connections

+ + + + + + + + + + + +
Destination IPPort
+ \ No newline at end of file diff --git a/testing/unit/tls/reports/tls_report_ext_local.md b/testing/unit/tls/reports/tls_report_ext_local.md deleted file mode 100644 index 878fa0743..000000000 --- a/testing/unit/tls/reports/tls_report_ext_local.md +++ /dev/null @@ -1,33 +0,0 @@ -# TLS Module - -### Certificate -| Property | Value | -|---|---| -| Version | 3 (0x2) | -| Signature Alg. | sha256WithRSAEncryption | -| Validity from | 2022-07-26 15:33:09 | -| Valid to | 2027-07-25 15:33:09 | - -### Subject -| Distinguished Name | Value | -|---|---| -| C | US -| CN | apc27D605.nam.gad.schneider-electric.com - -### Issuer -| Distinguished Name | Value | -|---|---| -| C | US -| O | IT Division -| CN | Sub CA - -### Extensions -| Extension | Value | -|---|---| -| subjectAltName | ap9643_qa1941270129.nam.gad.schneider-electric.com - -## Summary - -| # | Expiry | Length | Type | Port No. | Signed by | -|-------|---------------------------|----------|--------|------------|-------------| -| 1 | 2027-07-25 15:33:09 | 888 | EC | 443 | Sub CA | \ No newline at end of file diff --git a/testing/unit/tls/reports/tls_report_local.html b/testing/unit/tls/reports/tls_report_local.html new file mode 100644 index 000000000..610381444 --- /dev/null +++ b/testing/unit/tls/reports/tls_report_local.html @@ -0,0 +1,410 @@ +

TLS Module

+ + + + + + + + + + + + + + + + + + + + + + +
ExpiryLengthTypePort numberSigned by
2049-12-31 23:59:59779EC35288None
+ +
+
+
Certificate Information
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
Version3 (0x2)
Signature Alg.sha256WithRSAEncryption
Validity from2023-03-29 18:37:51
Valid to2049-12-31 23:59:59
+ +
+
+
Subject Information
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
CUS
STPennsylvania
LCoopersburg
OLutron Electronics Co.\, Inc.
CNathena04E580B9
+
+
+ + +
Certificate Extensions
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
authorityKeyIdentifierkey_identifier=accca4f9bd2a47dae81a8f4c87ed2c8edcfd07bf, authority_cert_issuer=None, authority_cert_serial_number=None
subjectKeyIdentifierdigest=37d90a274635e963081520f98411bda240d30252
basicConstraintsca=False, path_length=None
keyUsagedigital_signature=True, key_cert_sign=False, key_encipherment=False, crl_sign=False
+ + + + + + + + + + + + + + + + + + + + + + + +
ExpiryLengthTypePort numberSigned by
2119-02-05 00:00:00619EC443AthenaProcessor685E1CCB6ECB
+ +
+
+
Certificate Information
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
Version3 (0x2)
Signature Alg.ecdsa-with-SHA256
Validity from2019-03-01 00:00:00
Valid to2119-02-05 00:00:00
+ +
+
+
Subject Information
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
CUS
STPennsylvania
LCoopersburg
OLutron Electronics Co.\, Inc.
CNIPLServer4E580B9
+
+
+ + +
Certificate Extensions
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
keyUsagedigital_signature=True, key_cert_sign=False, key_encipherment=True, crl_sign=False
extendedKeyUsageserverAuth, Unknown OID
authorityKeyIdentifierkey_identifier=dff100033b0ab36497bbcd2f3e0515ea7b2f7ea0, authority_cert_issuer=None, authority_cert_serial_number=None
subjectAltNameIPLServer4E580B9
+ + + + + + + + + + + + + + + + + + + + + + + +
ExpiryLengthTypePort numberSigned by
2049-12-31 23:59:59779EC47188None
+ +
+
+
Certificate Information
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
Version3 (0x2)
Signature Alg.sha256WithRSAEncryption
Validity from2023-03-29 18:37:51
Valid to2049-12-31 23:59:59
+ +
+
+
Subject Information
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
CUS
STPennsylvania
LCoopersburg
OLutron Electronics Co.\, Inc.
CNathena04E580B9
+
+
+ + +
Certificate Extensions
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
authorityKeyIdentifierkey_identifier=accca4f9bd2a47dae81a8f4c87ed2c8edcfd07bf, authority_cert_issuer=None, authority_cert_serial_number=None
subjectKeyIdentifierdigest=37d90a274635e963081520f98411bda240d30252
basicConstraintsca=False, path_length=None
keyUsagedigital_signature=True, key_cert_sign=False, key_encipherment=False, crl_sign=False
+

Outbound Connections

+ + + + + + + + + + + + + + + + + +
Destination IPPort
224.0.0.2515353
209.244.0.3Unknown
3.227.250.136443
3.227.203.88443
34.226.101.2528883
3.227.250.208443
52.94.225.110443
+ \ No newline at end of file diff --git a/testing/unit/tls/reports/tls_report_local.md b/testing/unit/tls/reports/tls_report_local.md deleted file mode 100644 index dc3866dc6..000000000 --- a/testing/unit/tls/reports/tls_report_local.md +++ /dev/null @@ -1,35 +0,0 @@ -# TLS Module - -### Certificate -| Property | Value | -|---|---| -| Version | 1 (0x0) | -| Signature Alg. | sha256WithRSAEncryption | -| Validity from | 2022-09-21 19:57:57 | -| Valid to | 2027-09-21 19:57:57 | - -### Subject -| Distinguished Name | Value | -|---|---| -| C | US -| ST | California -| L | Concord -| O | BuildingsIoT -| OU | Software -| CN | EasyIO_FS-32 - -### Issuer -| Distinguished Name | Value | -|---|---| -| C | US -| ST | California -| L | Concord -| O | BuildingsIoT -| OU | Software -| CN | BuildingsIoT RSA Signing CA - -## Summary - -| # | Expiry | Length | Type | Port No. | Signed by | -|-------|---------------------------|----------|--------|------------|-------------| -| 1 | 2027-09-21 19:57:57 | 901 | RSA | 443 | BuildingsIoT RSA Signing CA | \ No newline at end of file diff --git a/testing/unit/tls/reports/tls_report_no_cert_local.html b/testing/unit/tls/reports/tls_report_no_cert_local.html new file mode 100644 index 000000000..c025ee9e8 --- /dev/null +++ b/testing/unit/tls/reports/tls_report_no_cert_local.html @@ -0,0 +1,5 @@ +

TLS Module

+
+
+ No TLS certificates found on the device +
\ No newline at end of file diff --git a/testing/unit/tls/reports/tls_report_no_cert_local.md b/testing/unit/tls/reports/tls_report_no_cert_local.md deleted file mode 100644 index 6de5bb88a..000000000 --- a/testing/unit/tls/reports/tls_report_no_cert_local.md +++ /dev/null @@ -1,9 +0,0 @@ -# TLS Module - -- No device certificates detected - - -## Summary - -| # | Expiry | Length | Type | Port No. | Signed by | -|-------|---------------------------|----------|--------|------------|-------------| \ No newline at end of file diff --git a/testing/unit/tls/reports/tls_report_single.html b/testing/unit/tls/reports/tls_report_single.html new file mode 100644 index 000000000..6106068a6 --- /dev/null +++ b/testing/unit/tls/reports/tls_report_single.html @@ -0,0 +1,118 @@ +

TLS Module

+ + + + + + + + + + + + + + + + + + + + + + +
ExpiryLengthTypePort numberSigned by
2027-09-21 19:57:57901RSA443BuildingsIoT RSA Signing CA
+ +
+
+
Certificate Information
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
Version1 (0x0)
Signature Alg.sha256WithRSAEncryption
Validity from2022-09-21 19:57:57
Valid to2027-09-21 19:57:57
+ +
+
+
Subject Information
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
CUS
STCalifornia
LConcord
OBuildingsIoT
OUSoftware
CNEasyIO_FS-32
+
+
+ + +

Outbound Connections

+ + + + + + + + + + + +
Destination IPPort
+ \ No newline at end of file diff --git a/testing/unit/tls/tls_module_test.py b/testing/unit/tls/tls_module_test.py index fc37ade40..f42c7e9d4 100644 --- a/testing/unit/tls/tls_module_test.py +++ b/testing/unit/tls/tls_module_test.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Module run all the TLS related unit tests""" +from tls_module import TLSModule from tls_util import TLSUtil import os import unittest @@ -38,9 +39,11 @@ CERT_DIR = os.path.join(TEST_FILES_DIR, 'certs/') ROOT_CERTS_DIR = os.path.join(TEST_FILES_DIR, 'root_certs') -LOCAL_REPORT = os.path.join(REPORTS_DIR, 'tls_report_local.md') -LOCAL_REPORT_EXT = os.path.join(REPORTS_DIR, 'tls_report_ext_local.md') -LOCAL_REPORT_NO_CERT = os.path.join(REPORTS_DIR, 'tls_report_no_cert_local.md') +LOCAL_REPORT = os.path.join(REPORTS_DIR, 'tls_report_local.html') +LOCAL_REPORT_SINGLE = os.path.join(REPORTS_DIR, 'tls_report_single.html') +LOCAL_REPORT_EXT = os.path.join(REPORTS_DIR, 'tls_report_ext_local.html') +LOCAL_REPORT_NO_CERT = os.path.join(REPORTS_DIR, + 'tls_report_no_cert_local.html') CONF_FILE = 'modules/test/' + MODULE + '/conf/module_config.json' INTERNET_IFACE = 'eth0' @@ -197,6 +200,7 @@ def security_tls_v1_2_fail_server_test(self): self.assertFalse(test_results[0]) # Test 1.2 server when 1.3 and 1.2 failed connection is established + def security_tls_v1_2_none_server_test(self): tls_1_2_results = None, 'No cert' tls_1_3_results = None, 'No cert' @@ -225,7 +229,7 @@ def security_tls_client_skip_test(self): capture_file = os.path.join(CAPTURES_DIR, 'no_tls.pcap') # Run the client test - test_results = TLS_UTIL.validate_tls_client(client_ip='172.27.253.167', + test_results = TLS_UTIL.validate_tls_client(client_mac='00:15:5d:0c:86:b9', tls_version='1.2', capture_files=[capture_file]) print(str(test_results)) @@ -269,8 +273,8 @@ def test_client_tls(self, os.makedirs(OUTPUT_DIR, exist_ok=True) capture_file = OUTPUT_DIR + '/client_tls.pcap' - # Resolve the client ip used - client_ip = self.get_interface_ip(INTERNET_IFACE) + # Resolve the client mac used + client_mac = self.get_interface_mac(INTERNET_IFACE) # Genrate TLS outbound traffic if tls_generate is None: @@ -278,7 +282,7 @@ def test_client_tls(self, self.generate_tls_traffic(capture_file, tls_generate, disable_valid_ciphers) # Run the client test - return TLS_UTIL.validate_tls_client(client_ip=client_ip, + return TLS_UTIL.validate_tls_client(client_mac=client_mac, tls_version=tls_version, capture_files=[capture_file]) @@ -287,7 +291,7 @@ def test_client_tls_with_non_tls_client(self): capture_file = os.path.join(CAPTURES_DIR, 'monitor.pcap') # Run the client test - test_results = TLS_UTIL.validate_tls_client(client_ip='10.10.10.14', + test_results = TLS_UTIL.validate_tls_client(client_mac='70:b3:d5:96:c0:00', tls_version='1.2', capture_files=[capture_file]) print(str(test_results)) @@ -300,7 +304,7 @@ def security_tls_client_unsupported_tls_client(self): capture_file = os.path.join(CAPTURES_DIR, 'unsupported_tls.pcap') # Run the client test - test_results = TLS_UTIL.validate_tls_client(client_ip='172.27.253.167', + test_results = TLS_UTIL.validate_tls_client(client_mac='00:15:5d:0c:86:b9', tls_version='1.2', capture_files=[capture_file]) print(str(test_results)) @@ -313,83 +317,135 @@ def security_tls_client_allowed_protocols_test(self): capture_file = os.path.join(CAPTURES_DIR, 'monitor_with_quic.pcap') # Run the client test - test_results = TLS_UTIL.validate_tls_client(client_ip='10.10.10.15', + test_results = TLS_UTIL.validate_tls_client(client_mac='e4:5f:01:5f:92:9c', tls_version='1.2', capture_files=[capture_file]) print(str(test_results)) self.assertTrue(test_results[0]) - # Commented out whilst TLS report is recreated - # def tls_module_report_test(self): - # print('\ntls_module_report_test') - # os.environ['DEVICE_MAC'] = '38:d1:35:01:17:fe' - # pcap_file = os.path.join(CAPTURES_DIR, 'tls.pcap') - # tls = TLSModule(module=MODULE, - # log_dir=OUTPUT_DIR, - # conf_file=CONF_FILE, - # results_dir=OUTPUT_DIR, - # startup_capture_file=pcap_file, - # monitor_capture_file=pcap_file, - # tls_capture_file=pcap_file) - # report_out_path = tls.generate_module_report() - - # with open(report_out_path, 'r', encoding='utf-8') as file: - # report_out = file.read() - - # # Read the local good report - # with open(LOCAL_REPORT, 'r', encoding='utf-8') as file: - # report_local = file.read() - - # self.assertEqual(report_out, report_local) - - # Commented out whilst TLS report is recreated - # def tls_module_report_ext_test(self): - # print('\ntls_module_report_ext_test') - # os.environ['DEVICE_MAC'] = '28:29:86:27:d6:05' - # pcap_file = os.path.join(CAPTURES_DIR, 'tls_ext.pcap') - # tls = TLSModule(module=MODULE, - # log_dir=OUTPUT_DIR, - # conf_file=CONF_FILE, - # results_dir=OUTPUT_DIR, - # startup_capture_file=pcap_file, - # monitor_capture_file=pcap_file, - # tls_capture_file=pcap_file) - # report_out_path = tls.generate_module_report() - - # # Read the generated report - # with open(report_out_path, 'r', encoding='utf-8') as file: - # report_out = file.read() - - # # Read the local good report - # with open(LOCAL_REPORT_EXT, 'r', encoding='utf-8') as file: - # report_local = file.read() - - # self.assertEqual(report_out, report_local) - - # Commented out whilst TLS report is recreated - # def tls_module_report_no_cert_test(self): - # print('\ntls_module_report_no_cert_test') - # os.environ['DEVICE_MAC'] = '' - # pcap_file = os.path.join(CAPTURES_DIR, 'tls_ext.pcap') - # tls = TLSModule(module=MODULE, - # log_dir=OUTPUT_DIR, - # conf_file=CONF_FILE, - # results_dir=OUTPUT_DIR, - # startup_capture_file=pcap_file, - # monitor_capture_file=pcap_file, - # tls_capture_file=pcap_file) - - # report_out_path = tls.generate_module_report() - - # # Read the generated report - # with open(report_out_path, 'r', encoding='utf-8') as file: - # report_out = file.read() - - # # Read the local good report - # with open(LOCAL_REPORT_NO_CERT, 'r', encoding='utf-8') as file: - # report_local = file.read() - - # self.assertEqual(report_out, report_local) + def outbound_connections_test(self): + """ Test generation of the outbound connection ips""" + print('\noutbound_connections_test') + capture_file = os.path.join(CAPTURES_DIR, 'monitor.pcap') + ip_dst = TLS_UTIL.get_all_outbound_connections( + device_mac='70:b3:d5:96:c0:00', capture_files=[capture_file]) + print(str(ip_dst)) + # Expected set of IPs and ports in tuple format + expected_ips = { + ('216.239.35.0', 123), + ('8.8.8.8', 'Unknown'), + ('8.8.8.8', 53), + ('18.140.82.197', 443), + ('18.140.82.197', 22), + ('224.0.0.22', 'Unknown'), + ('18.140.82.197', 80) + } + # Compare as sets since returned order is not guaranteed + self.assertEqual( + set(ip_dst), + expected_ips) + + def outbound_connections_report_test(self): + """ Test generation of the outbound connection ips""" + print('\noutbound_connections_report_test') + capture_file = os.path.join(CAPTURES_DIR, 'monitor.pcap') + ip_dst = TLS_UTIL.get_all_outbound_connections( + device_mac='70:b3:d5:96:c0:00', capture_files=[capture_file]) + tls = TLSModule(module=MODULE) + gen_html = tls.generate_outbound_connection_table(ip_dst) + print(gen_html) + + def tls_module_report_multi_page_test(self): + print('\ntls_module_report_test') + os.environ['DEVICE_MAC'] = '68:5e:1c:cb:6e:cb' + startup_pcap_file = os.path.join(CAPTURES_DIR, 'multi_page_startup.pcap') + monitor_pcap_file = os.path.join(CAPTURES_DIR, 'multi_page_monitor.pcap') + tls_pcap_file = os.path.join(CAPTURES_DIR, 'multi_page_tls.pcap') + tls = TLSModule(module=MODULE, + results_dir=OUTPUT_DIR, + startup_capture_file=startup_pcap_file, + monitor_capture_file=monitor_pcap_file, + tls_capture_file=tls_pcap_file) + report_out_path = tls.generate_module_report() + with open(report_out_path, 'r', encoding='utf-8') as file: + report_out = file.read() + + # Read the local good report + with open(LOCAL_REPORT, 'r', encoding='utf-8') as file: + report_local = file.read() + + self.assertEqual(report_out, report_local) + + def tls_module_report_test(self): + print('\ntls_module_report_test') + os.environ['DEVICE_MAC'] = '38:d1:35:01:17:fe' + pcap_file = os.path.join(CAPTURES_DIR, 'tls.pcap') + tls = TLSModule(module=MODULE, + results_dir=OUTPUT_DIR, + startup_capture_file=pcap_file, + monitor_capture_file=pcap_file, + tls_capture_file=pcap_file) + report_out_path = tls.generate_module_report() + with open(report_out_path, 'r', encoding='utf-8') as file: + report_out = file.read() + + # Read the local good report + with open(LOCAL_REPORT_SINGLE, 'r', encoding='utf-8') as file: + report_local = file.read() + self.assertEqual(report_out, report_local) + + def tls_module_report_ext_test(self): + print('\ntls_module_report_ext_test') + os.environ['DEVICE_MAC'] = '28:29:86:27:d6:05' + pcap_file = os.path.join(CAPTURES_DIR, 'tls_ext.pcap') + tls = TLSModule(module=MODULE, + results_dir=OUTPUT_DIR, + startup_capture_file=pcap_file, + monitor_capture_file=pcap_file, + tls_capture_file=pcap_file) + report_out_path = tls.generate_module_report() + + # Read the generated report + with open(report_out_path, 'r', encoding='utf-8') as file: + report_out = file.read() + + # Read the local good report + with open(LOCAL_REPORT_EXT, 'r', encoding='utf-8') as file: + report_local = file.read() + + # Copy the generated html report to a new file + new_report_name = 'tls_report_ext_local.html' + new_report_path = os.path.join(OUTPUT_DIR, new_report_name) + shutil.copy(report_out_path, new_report_path) + + self.assertEqual(report_out, report_local) + + def tls_module_report_no_cert_test(self): + print('\ntls_module_report_no_cert_test') + os.environ['DEVICE_MAC'] = '' + pcap_file = os.path.join(CAPTURES_DIR, 'tls_ext.pcap') + tls = TLSModule(module=MODULE, + results_dir=OUTPUT_DIR, + startup_capture_file=pcap_file, + monitor_capture_file=pcap_file, + tls_capture_file=pcap_file) + + report_out_path = tls.generate_module_report() + + # Read the generated report + with open(report_out_path, 'r', encoding='utf-8') as file: + report_out = file.read() + + # Read the local good report + with open(LOCAL_REPORT_NO_CERT, 'r', encoding='utf-8') as file: + report_local = file.read() + + # Copy the generated html report to a new file + new_report_name = 'tls_report_no_cert_local.html' + new_report_path = os.path.join(OUTPUT_DIR, new_report_name) + shutil.copy(report_out_path, new_report_path) + + self.assertEqual(report_out, report_local) def generate_tls_traffic(self, capture_file, @@ -470,11 +526,11 @@ def start_capture_thread(self, timeout): return capture_thread - def get_interface_ip(self, interface_name): + def get_interface_mac(self, interface_name): try: addresses = netifaces.ifaddresses(interface_name) - ipv4 = addresses[netifaces.AF_INET][0]['addr'] - return ipv4 + mac = addresses[netifaces.AF_LINK][0]['addr'] + return mac except (ValueError, KeyError) as e: print(f'Error: {e}') return None @@ -540,6 +596,7 @@ def download_public_cert(self, hostname, port=443): if __name__ == '__main__': suite = unittest.TestSuite() suite.addTest(TLSModuleTest('client_hello_packets_test')) + # TLS 1.2 server tests suite.addTest(TLSModuleTest('security_tls_v1_2_server_test')) suite.addTest(TLSModuleTest('security_tls_v1_2_for_1_3_server_test')) @@ -553,6 +610,7 @@ def download_public_cert(self, hostname, port=443): # # TLS 1.3 server tests suite.addTest(TLSModuleTest('security_tls_v1_3_server_test')) + # TLS client tests suite.addTest(TLSModuleTest('security_tls_v1_2_client_test')) suite.addTest(TLSModuleTest('security_tls_v1_3_client_test')) @@ -564,10 +622,11 @@ def download_public_cert(self, hostname, port=443): # Test the results options for tls server tests suite.addTest(TLSModuleTest('security_tls_server_results_test')) - # # Test various report module outputs - # suite.addTest(TLSModuleTest('tls_module_report_test')) - # suite.addTest(TLSModuleTest('tls_module_report_ext_test')) - # suite.addTest(TLSModuleTest('tls_module_report_no_cert_test')) + # Test various report module outputs + suite.addTest(TLSModuleTest('tls_module_report_test')) + suite.addTest(TLSModuleTest('tls_module_report_ext_test')) + suite.addTest(TLSModuleTest('tls_module_report_no_cert_test')) + suite.addTest(TLSModuleTest('tls_module_report_multi_page_test')) # Test signature validation methods suite.addTest(TLSModuleTest('tls_module_trusted_ca_cert_chain_test')) @@ -576,6 +635,9 @@ def download_public_cert(self, hostname, port=443): suite.addTest(TLSModuleTest('security_tls_client_allowed_protocols_test')) + suite.addTest(TLSModuleTest('outbound_connections_test')) + suite.addTest(TLSModuleTest('outbound_connections_report_test')) + runner = unittest.TextTestRunner() test_result = runner.run(suite) From 985d58f6483680123a33ecf344cdb457ceb4cdcb Mon Sep 17 00:00:00 2001 From: Jacob Boddey Date: Wed, 12 Feb 2025 11:56:57 +0000 Subject: [PATCH 05/12] Update dependencies and bump version --- framework/requirements.txt | 2 +- make/DEBIAN/control | 2 +- modules/test/conn/python/requirements.txt | 2 +- modules/test/tls/python/requirements.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/requirements.txt b/framework/requirements.txt index 1c3f673a7..b5726aca2 100644 --- a/framework/requirements.txt +++ b/framework/requirements.txt @@ -28,7 +28,7 @@ responses==0.25.3 markdown==3.5.2 # Requirements for the session -cryptography==44.0.0 +cryptography==44.0.1 pytz==2024.1 # Requirements for the risk profile diff --git a/make/DEBIAN/control b/make/DEBIAN/control index d4024828e..2a0235082 100644 --- a/make/DEBIAN/control +++ b/make/DEBIAN/control @@ -1,5 +1,5 @@ Package: Testrun -Version: 2.1 +Version: 2.1.1 Architecture: amd64 Maintainer: Google Homepage: https://github.com/google/testrun diff --git a/modules/test/conn/python/requirements.txt b/modules/test/conn/python/requirements.txt index 4075f79c9..d0f5db19a 100644 --- a/modules/test/conn/python/requirements.txt +++ b/modules/test/conn/python/requirements.txt @@ -2,7 +2,7 @@ # Package dependencies should always be defined before the user defined # packages to prevent auto-upgrades of stable dependencies cffi==1.17.1 -cryptography==43.0.3 +cryptography==44.0.1 pycparser==2.22 six==1.16.0 diff --git a/modules/test/tls/python/requirements.txt b/modules/test/tls/python/requirements.txt index d7180f31b..2cbc1db46 100644 --- a/modules/test/tls/python/requirements.txt +++ b/modules/test/tls/python/requirements.txt @@ -13,7 +13,7 @@ termcolor==2.4.0 urllib3==2.2.2 # User defined packages -cryptography==43.0.1 +cryptography==44.0.1 pyOpenSSL==24.3.0 lxml==5.1.0 # Requirement of pyshark but if upgraded automatically above 5.1 will cause a pyshark==0.6 From 495d8e4204312469231ca1876ad4c54c7de68c1f Mon Sep 17 00:00:00 2001 From: MariusBaldovin Date: Wed, 12 Feb 2025 12:13:57 +0000 Subject: [PATCH 06/12] fix failed tests --- framework/python/src/common/testreport.py | 2 - resources/test_packs/pilot.json | 169 --------------------- resources/test_packs/qualification.json | 177 ---------------------- testing/unit/report/report_test.py | 84 ---------- 4 files changed, 432 deletions(-) delete mode 100644 resources/test_packs/pilot.json delete mode 100644 resources/test_packs/qualification.json diff --git a/framework/python/src/common/testreport.py b/framework/python/src/common/testreport.py index fbd993a8e..70603ef6c 100644 --- a/framework/python/src/common/testreport.py +++ b/framework/python/src/common/testreport.py @@ -50,8 +50,6 @@ report_resource_dir = os.path.join(root_dir, RESOURCES_DIR) test_run_img_file = os.path.join(report_resource_dir, 'testrun.png') -qualification_icon = os.path.join(report_resource_dir, 'qualification-icon.png') -pilot_icon = os.path.join(report_resource_dir, 'pilot-icon.png') class TestReport(): diff --git a/resources/test_packs/pilot.json b/resources/test_packs/pilot.json deleted file mode 100644 index 587d0a25a..000000000 --- a/resources/test_packs/pilot.json +++ /dev/null @@ -1,169 +0,0 @@ -{ - "name": "Pilot Assessment", - "language": { - "compliant_description": "Your device has met the initial pilot assessment requirements. Please send your Testrun ZIP file to the qualification lab for verification. The lab will then contact you with further instructions.", - "non_compliant_description": "Your device didn't quite meet the initial pilot assessment requirements. The Testrun report will provide guidance on how to resolve any issues. If you require further support, please get in touch with the qualification lab." - }, - "tests": [ - { - "name": "connection.port_link", - "required_result": "Informational" - }, - { - "name": "connection.port_speed", - "required_result": "Informational" - }, - { - "name": "connection.port_duplex", - "required_result": "Informational" - }, - { - "name": "connection.switch.arp_inspection", - "required_result": "Informational" - }, - { - "name": "connection.switch.dhcp_snooping", - "required_result": "Informational" - }, - { - "name": "connection.dhcp_address", - "required_result": "Required" - }, - { - "name": "connection.mac_address", - "required_result": "Required" - }, - { - "name": "connection.mac_oui", - "required_result": "Informational" - }, - { - "name": "connection.private_address", - "required_result": "Informational" - }, - { - "name": "connection.shared_address", - "required_result": "Informational" - }, - { - "name": "connection.single_ip", - "required_result": "Informational" - }, - { - "name": "connection.target_ping", - "required_result": "Informational" - }, - { - "name": "connection.ipaddr.ip_change", - "required_result": "Informational" - }, - { - "name": "connection.ipaddr.dhcp_failover", - "required_result": "Informational" - }, - { - "name": "connection.ipv6_slaac", - "required_result": "Informational" - }, - { - "name": "connection.ipv6_ping", - "required_result": "Informational" - }, - { - "name": "dns.network.hostname_resolution", - "required_result": "Informational" - }, - { - "name": "dns.network.from_dhcp", - "required_result": "Informational" - }, - { - "name": "dns.mdns", - "required_result": "Informational" - }, - { - "name": "ntp.network.ntp_support", - "required_result": "Informational" - }, - { - "name": "ntp.network.ntp_dhcp", - "required_result": "Informational" - }, - { - "name": "protocol.valid_bacnet", - "required_result": "Informational" - }, - { - "name": "protocol.bacnet.version", - "required_result": "Informational" - }, - { - "name": "protocol.valid_modbus", - "required_result": "Informational" - }, - { - "name": "security.services.ftp", - "required_result": "Informational" - }, - { - "name": "security.ssh.version", - "required_result": "Informational" - }, - { - "name": "security.services.telnet", - "required_result": "Informational" - }, - { - "name": "security.services.smtp", - "required_result": "Informational" - }, - { - "name": "security.services.http", - "required_result": "Informational" - }, - { - "name": "security.services.pop", - "required_result": "Informational" - }, - { - "name": "security.services.imap", - "required_result": "Informational" - }, - { - "name": "security.services.snmpv3", - "required_result": "Informational" - }, - { - "name": "security.services.vnc", - "required_result": "Informational" - }, - { - "name": "security.services.tftp", - "required_result": "Informational" - }, - { - "name": "ntp.network.ntp_server", - "required_result": "Informational" - }, - { - "name": "security.tls.v1_0_client", - "required_result": "Required if Applicable" - }, - { - "name": "security.tls.v1_2_server", - "required_result": "Informational" - }, - { - "name": "security.tls.v1_2_client", - "required_result": "Informational" - }, - { - "name": "security.tls.v1_3_server", - "required_result": "Informational" - }, - { - "name": "security.tls.v1_3_client", - "required_result": "Informational" - } - ] - } \ No newline at end of file diff --git a/resources/test_packs/qualification.json b/resources/test_packs/qualification.json deleted file mode 100644 index 967370b4a..000000000 --- a/resources/test_packs/qualification.json +++ /dev/null @@ -1,177 +0,0 @@ -{ - "name": "Device Qualification", - "language": { - "compliant_description": "Your device has met the initial device qualification requirements. Please send your Testrun ZIP file to the qualification lab for verification. The lab will then contact you with further instructions.", - "non_compliant_description": "Your device didn't quite meet the initial device qualification requirements. The Testrun report will provide guidance on how to resolve any issues. If you require further support, please get in touch with the qualification lab." - }, - "tests": [ - { - "name": "connection.port_link", - "required_result": "Required" - }, - { - "name": "connection.port_speed", - "required_result": "Required" - }, - { - "name": "connection.port_duplex", - "required_result": "Required" - }, - { - "name": "connection.switch.arp_inspection", - "required_result": "Required" - }, - { - "name": "connection.switch.dhcp_snooping", - "required_result": "Required" - }, - { - "name": "connection.dhcp_address", - "required_result": "Required" - }, - { - "name": "connection.mac_address", - "required_result": "Required" - }, - { - "name": "connection.mac_oui", - "required_result": "Required" - }, - { - "name": "connection.private_address", - "required_result": "Required" - }, - { - "name": "connection.shared_address", - "required_result": "Required" - }, - { - "name": "connection.dhcp_disconnect", - "required_result": "Required" - }, - { - "name": "connection.dhcp_disconnect_ip_change", - "required_result": "Required" - }, - { - "name": "connection.single_ip", - "required_result": "Required" - }, - { - "name": "connection.target_ping", - "required_result": "Required" - }, - { - "name": "connection.ipaddr.ip_change", - "required_result": "Required" - }, - { - "name": "connection.ipaddr.dhcp_failover", - "required_result": "Required" - }, - { - "name": "connection.ipv6_slaac", - "required_result": "Required" - }, - { - "name": "connection.ipv6_ping", - "required_result": "Required" - }, - { - "name": "dns.network.hostname_resolution", - "required_result": "Required" - }, - { - "name": "dns.network.from_dhcp", - "required_result": "Informational" - }, - { - "name": "dns.mdns", - "required_result": "Informational" - }, - { - "name": "ntp.network.ntp_support", - "required_result": "Required" - }, - { - "name": "ntp.network.ntp_dhcp", - "required_result": "Roadmap" - }, - { - "name": "protocol.valid_bacnet", - "required_result": "Recommended" - }, - { - "name": "protocol.bacnet.version", - "required_result": "Recommended" - }, - { - "name": "protocol.valid_modbus", - "required_result": "Recommended" - }, - { - "name": "security.services.ftp", - "required_result": "Required" - }, - { - "name": "security.ssh.version", - "required_result": "Required" - }, - { - "name": "security.services.telnet", - "required_result": "Required" - }, - { - "name": "security.services.smtp", - "required_result": "Required" - }, - { - "name": "security.services.http", - "required_result": "Required" - }, - { - "name": "security.services.pop", - "required_result": "Required" - }, - { - "name": "security.services.imap", - "required_result": "Required" - }, - { - "name": "security.services.snmpv3", - "required_result": "Required" - }, - { - "name": "security.services.vnc", - "required_result": "Required" - }, - { - "name": "security.services.tftp", - "required_result": "Required" - }, - { - "name": "ntp.network.ntp_server", - "required_result": "Required" - }, - { - "name": "security.tls.v1_0_client", - "required_result": "Informational" - }, - { - "name": "security.tls.v1_2_server", - "required_result": "Required if Applicable" - }, - { - "name": "security.tls.v1_2_client", - "required_result": "Required if Applicable" - }, - { - "name": "security.tls.v1_3_server", - "required_result": "Informational" - }, - { - "name": "security.tls.v1_3_client", - "required_result": "Informational" - } - ] -} \ No newline at end of file diff --git a/testing/unit/report/report_test.py b/testing/unit/report/report_test.py index 78f82cb4e..d1093ff28 100644 --- a/testing/unit/report/report_test.py +++ b/testing/unit/report/report_test.py @@ -204,87 +204,6 @@ def add_html_formatting(self, body): return template.render(styles=styles, body=body, device=device) - # Generate formatted reports for each report generated from - # the test containers. - # Not a unit test but can't run from within the test module container and must - # be done through the venv. Useful for doing visual inspections - # of report formatting changes without having to re-run a new device test. - def report_formatting(self): - """Apply formatting and generate HTML reports for visual inspection""" - - # List of modules for which to generate formatted reports - test_modules = ['conn','dns','ntp','protocol','services','tls'] - - # List all items from UNIT_TEST_DIR - unit_tests = os.listdir(UNIT_TEST_DIR) - - # Loop through each items from UNIT_TEST_DIR - for test in unit_tests: - - # If the module name inside the test_modules list - if test in test_modules: - - # Construct the module path of outpit dir for the module - output_dir = os.path.join(UNIT_TEST_DIR,test,'output') - - # Check if output dir exists - if os.path.isdir(output_dir): - # List all files fro output dir - output_files = os.listdir(output_dir) - - # Loop through each file - for file in output_files: - - # Chck if is an html file - if file.endswith('.html'): - - # Construct teh full path of html file - report_out_path = os.path.join(output_dir,file) - - # Open the html file in read mode - with open(report_out_path, 'r', encoding='utf-8') as f: - report_out = f.read() - # Add the formatting - formatted_report = self.add_html_formatting(report_out) - - # Write back the new formatted_report value - out_report_dir = os.path.join(OUTPUT_DIR, test) - os.makedirs(out_report_dir, exist_ok=True) - - with open(os.path.join( - out_report_dir,file), 'w', - encoding='utf-8') as f: - f.write(formatted_report) - - def add_html_formatting(self, body): - """Wrap the raw report inside a complete HTML structure with styles""" - - # Load the css file - with open(CSS_PATH, 'r', encoding='UTF-8') as css_file: - styles = css_file.read() - - # Load the html file - with open(HTML_PATH, 'r', encoding='UTF-8') as html_file: - html_content = html_file.read() - - # Search for head content using regex - head = re.search(r'.*?', html_content, re.DOTALL).group(0) - # Define the html template - html_template = f''' - - - {head} - - {body} - - - ''' - # Create a Jinja2 template from the string - template = Template(html_template) - - # Render the template with css styles - return template.render(styles=styles, body=body) - def get_module_html_report(self, module): """Load the HTML report for a specific module""" @@ -312,8 +231,5 @@ def get_module_html_report(self, module): # Create html test reports for each module in 'output' dir suite.addTest(ReportTest('report_formatting')) - # Create html test reports for each module in 'output' dir - suite.addTest(ReportTest('report_formatting')) - runner = unittest.TextTestRunner() runner.run(suite) From 8ba6792c5b9bc1b9c9d016b8cde4cdaf7980c706 Mon Sep 17 00:00:00 2001 From: Sofia Kurilova Date: Wed, 12 Feb 2025 13:38:08 +0000 Subject: [PATCH 07/12] Fix mock (#1116) --- modules/ui/src/app/mocks/testrun.mock.ts | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/modules/ui/src/app/mocks/testrun.mock.ts b/modules/ui/src/app/mocks/testrun.mock.ts index 8eecb1495..48321f2d5 100644 --- a/modules/ui/src/app/mocks/testrun.mock.ts +++ b/modules/ui/src/app/mocks/testrun.mock.ts @@ -43,11 +43,6 @@ export const TEST_DATA_RESULT: IResult[] = [ result: 'Not Started', required_result: RequiredResult.RequiredIfApplicable, }, - { - name: 'dns.mdns', - description: 'Does the device has MDNS (or any kind of IP multicast)', - result: 'Not Started', - }, ]; export const TEST_DATA_RESULT_WITH_RECOMMENDATIONS: IResult[] = [ @@ -86,25 +81,6 @@ export const TEST_DATA_RESULT_WITH_ERROR: IResult[] = [ }, ]; -export const TEST_DATA_RESULT_WITH_ERROR: IResult[] = [ - { - name: 'dns.network.hostname_resolution', - description: 'The device should resolve hostnames', - result: 'Compliant', - }, - { - name: 'dns.network.from_dhcp', - description: - 'The device should use the DNS server provided by the DHCP server', - result: 'Error', - }, - { - name: 'dns.mdns', - description: 'Does the device has MDNS (or any kind of IP multicast)', - result: 'Not Started', - }, -]; - export const TEST_DATA_TABLE_RESULT: IResult[] = [ ...TEST_DATA_RESULT, ...new Array(23).fill(null).map(() => ({}) as IResult), From e2dd6559058b301180901463407bd73c4851f292 Mon Sep 17 00:00:00 2001 From: kurilova Date: Wed, 12 Feb 2025 12:03:44 +0000 Subject: [PATCH 08/12] Prevent send new request before previous is finished --- .../testrun-initiate-form.component.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts index cbec35a0a..0fddce670 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts +++ b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts @@ -59,6 +59,7 @@ export class TestrunInitiateFormComponent extends EscapableDialogComponent implements OnInit, AfterViewChecked { + private startRequestSent = new BehaviorSubject(false); @ViewChild('firmwareInput') firmwareInput!: ElementRef; initiateForm!: FormGroup; devices$ = this.store.select(selectDevices); @@ -165,7 +166,8 @@ export class TestrunInitiateFormComponent } ); - if (this.selectedDevice) { + if (this.selectedDevice && !this.startRequestSent.value) { + this.startRequestSent.next(true); this.testRunService.fetchVersion(); this.testRunService .startTestrun({ @@ -174,8 +176,13 @@ export class TestrunInitiateFormComponent test_modules: testModules, }) .pipe(take(1)) - .subscribe(status => { - this.cancel(status); + .subscribe({ + next: status => { + this.cancel(status); + }, + error: () => { + this.startRequestSent.next(false); + }, }); } } From c33e6428eb9500b4cec39ccb24bb22fb1d13e09e Mon Sep 17 00:00:00 2001 From: Marius <86727846+MariusBaldovin@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:04:55 +0000 Subject: [PATCH 09/12] remove steps_to_resolve logs (#1119) --- framework/python/src/common/testreport.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/framework/python/src/common/testreport.py b/framework/python/src/common/testreport.py index 70603ef6c..04d58a45c 100644 --- a/framework/python/src/common/testreport.py +++ b/framework/python/src/common/testreport.py @@ -270,8 +270,6 @@ def to_html(self): logic = current_test_pack.get_logic() steps_to_resolve_ = logic.get_steps_to_resolve(json_data) - LOGGER.debug(steps_to_resolve_) - module_reports = self._module_reports env_module = Environment(loader=BaseLoader()) pages_num = self._pages_num(json_data) From 947a5bbc7ebf5c35ed68c0a0c8d5e4dc6c645d3e Mon Sep 17 00:00:00 2001 From: kurilova Date: Fri, 14 Feb 2025 15:39:09 +0000 Subject: [PATCH 10/12] Remove word Preliminary from pdf report --- resources/test_packs/pilot/report_templates/summary.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/test_packs/pilot/report_templates/summary.jinja b/resources/test_packs/pilot/report_templates/summary.jinja index 7e5186a7f..bedf6d383 100644 --- a/resources/test_packs/pilot/report_templates/summary.jinja +++ b/resources/test_packs/pilot/report_templates/summary.jinja @@ -39,7 +39,7 @@
Complete
Test Result
{{ json_data['result'] }}
-
Preliminary Pilot Recommendation
+
Pilot Recommendation
{{ json_data['status'] }}
Started
{{ json_data['started']}}
From 4ae25a354066589c6cd50bfcd6c06623abf4bf3e Mon Sep 17 00:00:00 2001 From: Jacob Boddey Date: Fri, 21 Feb 2025 16:24:39 +0000 Subject: [PATCH 11/12] Fix merge issues --- modules/ui/.gitignore | 2 + modules/ui/package-lock.json | 15949 ------------------- resources/report/header_macros.jinja | 43 - resources/report/test_report_template.html | 241 - 4 files changed, 2 insertions(+), 16233 deletions(-) delete mode 100644 modules/ui/package-lock.json delete mode 100644 resources/report/header_macros.jinja delete mode 100644 resources/report/test_report_template.html diff --git a/modules/ui/.gitignore b/modules/ui/.gitignore index d53004b88..1b50068e8 100644 --- a/modules/ui/.gitignore +++ b/modules/ui/.gitignore @@ -41,3 +41,5 @@ testem.log # System files .DS_Store Thumbs.db + +package-lock.json diff --git a/modules/ui/package-lock.json b/modules/ui/package-lock.json deleted file mode 100644 index 1e4edf90f..000000000 --- a/modules/ui/package-lock.json +++ /dev/null @@ -1,15949 +0,0 @@ -{ - "name": "test-run-ui", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "test-run-ui", - "version": "0.0.0", - "dependencies": { - "@angular/animations": "^18.2.4", - "@angular/cdk": "^18.2.0", - "@angular/common": "^18.2.4", - "@angular/compiler": "^18.2.4", - "@angular/core": "^18.2.4", - "@angular/forms": "^18.2.4", - "@angular/material": "^18.2.0", - "@angular/platform-browser": "^18.2.4", - "@angular/platform-browser-dynamic": "^18.2.4", - "@angular/router": "^18.2.4", - "@ngrx/component-store": "^18.0.2", - "@ngrx/effects": "^18.0.2", - "@ngrx/store": "^18.0.2", - "ngx-mask": "^16.4.2", - "ngx-mqtt": "^17.0.0", - "rxjs": "~7.8.0", - "tslib": "^2.6.2", - "zone.js": "^0.14.10" - }, - "devDependencies": { - "@angular-devkit/build-angular": "^18.1.4", - "@angular-eslint/builder": "18.3.0", - "@angular-eslint/eslint-plugin": "^18.3.0", - "@angular-eslint/eslint-plugin-template": "^18.3.0", - "@angular-eslint/schematics": "^18.3.0", - "@angular-eslint/template-parser": "18.3.0", - "@angular/cli": "~18.2.4", - "@angular/compiler-cli": "^18.2.4", - "@types/jasmine": "~4.3.6", - "@typescript-eslint/eslint-plugin": "^8.2.0", - "@typescript-eslint/parser": "^8.2.0", - "eslint": "^8.57.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-prettier": "^5.1.3", - "jasmine-core": "~4.6.0", - "karma": "~6.4.0", - "karma-chrome-launcher": "~3.2.0", - "karma-coverage": "~2.2.0", - "karma-jasmine": "~5.1.0", - "karma-jasmine-html-reporter": "~2.1.0", - "prettier": "^3.2.5", - "prettier-eslint": "^13.0.0", - "typescript": "~5.5.4" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@angular-devkit/architect": { - "version": "0.1802.14", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.14.tgz", - "integrity": "sha512-eplaGCXSlPwf1f4XwyzsYTd8/lJ0/Adm6XsODsBxvkZlIpLcps80/h2lH5MVJpoDREzIFu1BweDpYCoNK5yYZg==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "18.2.14", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/build-angular": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.14.tgz", - "integrity": "sha512-ycie4OhvNv8eNVqvq46pCIf6kB50xbMOdnAVqmlj+BaQjWbGjUQPjAmp4VGqeDZZ/lW82xkfTmJZxc6pYp7YdQ==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1802.14", - "@angular-devkit/build-webpack": "0.1802.14", - "@angular-devkit/core": "18.2.14", - "@angular/build": "18.2.14", - "@babel/core": "7.25.2", - "@babel/generator": "7.25.0", - "@babel/helper-annotate-as-pure": "7.24.7", - "@babel/helper-split-export-declaration": "7.24.7", - "@babel/plugin-transform-async-generator-functions": "7.25.0", - "@babel/plugin-transform-async-to-generator": "7.24.7", - "@babel/plugin-transform-runtime": "7.24.7", - "@babel/preset-env": "7.25.3", - "@babel/runtime": "7.25.0", - "@discoveryjs/json-ext": "0.6.1", - "@ngtools/webpack": "18.2.14", - "@vitejs/plugin-basic-ssl": "1.1.0", - "ansi-colors": "4.1.3", - "autoprefixer": "10.4.20", - "babel-loader": "9.1.3", - "browserslist": "^4.21.5", - "copy-webpack-plugin": "12.0.2", - "critters": "0.0.24", - "css-loader": "7.1.2", - "esbuild-wasm": "0.23.0", - "fast-glob": "3.3.2", - "http-proxy-middleware": "3.0.3", - "https-proxy-agent": "7.0.5", - "istanbul-lib-instrument": "6.0.3", - "jsonc-parser": "3.3.1", - "karma-source-map-support": "1.4.0", - "less": "4.2.0", - "less-loader": "12.2.0", - "license-webpack-plugin": "4.0.2", - "loader-utils": "3.3.1", - "magic-string": "0.30.11", - "mini-css-extract-plugin": "2.9.0", - "mrmime": "2.0.0", - "open": "10.1.0", - "ora": "5.4.1", - "parse5-html-rewriting-stream": "7.0.0", - "picomatch": "4.0.2", - "piscina": "4.6.1", - "postcss": "8.4.41", - "postcss-loader": "8.1.1", - "resolve-url-loader": "5.0.0", - "rxjs": "7.8.1", - "sass": "1.77.6", - "sass-loader": "16.0.0", - "semver": "7.6.3", - "source-map-loader": "5.0.0", - "source-map-support": "0.5.21", - "terser": "5.31.6", - "tree-kill": "1.2.2", - "tslib": "2.6.3", - "watchpack": "2.4.1", - "webpack": "5.94.0", - "webpack-dev-middleware": "7.4.2", - "webpack-dev-server": "5.0.4", - "webpack-merge": "6.0.1", - "webpack-subresource-integrity": "5.1.0" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "optionalDependencies": { - "esbuild": "0.23.0" - }, - "peerDependencies": { - "@angular/compiler-cli": "^18.0.0", - "@angular/localize": "^18.0.0", - "@angular/platform-server": "^18.0.0", - "@angular/service-worker": "^18.0.0", - "@web/test-runner": "^0.18.0", - "browser-sync": "^3.0.2", - "jest": "^29.5.0", - "jest-environment-jsdom": "^29.5.0", - "karma": "^6.3.0", - "ng-packagr": "^18.0.0", - "protractor": "^7.0.0", - "tailwindcss": "^2.0.0 || ^3.0.0", - "typescript": ">=5.4 <5.6" - }, - "peerDependenciesMeta": { - "@angular/localize": { - "optional": true - }, - "@angular/platform-server": { - "optional": true - }, - "@angular/service-worker": { - "optional": true - }, - "@web/test-runner": { - "optional": true - }, - "browser-sync": { - "optional": true - }, - "jest": { - "optional": true - }, - "jest-environment-jsdom": { - "optional": true - }, - "karma": { - "optional": true - }, - "ng-packagr": { - "optional": true - }, - "protractor": { - "optional": true - }, - "tailwindcss": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", - "dev": true - }, - "node_modules/@angular-devkit/build-webpack": { - "version": "0.1802.14", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.14.tgz", - "integrity": "sha512-cccne0SG4BaQHsKRRZCi/wMLJ7yFXrwvE8w+Kug3HdpJJoyH3FeG386EQuca/azslQlK+c5g4ywSZdXeNkGazA==", - "dev": true, - "dependencies": { - "@angular-devkit/architect": "0.1802.14", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "webpack": "^5.30.0", - "webpack-dev-server": "^5.0.2" - } - }, - "node_modules/@angular-devkit/core": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.14.tgz", - "integrity": "sha512-UGIGOjXuOyCW+5S4tINu7e6LOu738CmTw3h7Ui1I8OzdTIYJcYJrei8sgrwDwOYADRal+p0MeMlnykH3TM5XBA==", - "dev": true, - "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.2", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/schematics": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.14.tgz", - "integrity": "sha512-mukjZIHHB7gWratq8fZwUq5WZ+1bF4feG/idXr1wgQ+/FqWjs2PP7HDesHVcPymmRulpTyCpB7TNB1O1fgnCpA==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "18.2.14", - "jsonc-parser": "3.3.1", - "magic-string": "0.30.11", - "ora": "5.4.1", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-eslint/builder": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-18.3.0.tgz", - "integrity": "sha512-httEQyqyBw3+0CRtAa7muFxHrauRfkEfk/jmrh5fn2Eiu+I53hAqFPgrwVi1V6AP/kj2zbAiWhd5xM3pMJdoRQ==", - "dev": true, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": "*" - } - }, - "node_modules/@angular-eslint/bundled-angular-compiler": { - "version": "18.4.3", - "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.4.3.tgz", - "integrity": "sha512-zdrA8mR98X+U4YgHzUKmivRU+PxzwOL/j8G7eTOvBuq8GPzsP+hvak+tyxlgeGm9HsvpFj9ERHLtJ0xDUPs8fg==", - "dev": true - }, - "node_modules/@angular-eslint/eslint-plugin": { - "version": "18.4.3", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-18.4.3.tgz", - "integrity": "sha512-AyJbupiwTBR81P6T59v+aULEnPpZBCBxL2S5QFWfAhNCwWhcof4GihvdK2Z87yhvzDGeAzUFSWl/beJfeFa+PA==", - "dev": true, - "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.4.3", - "@angular-eslint/utils": "18.4.3" - }, - "peerDependencies": { - "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": "*" - } - }, - "node_modules/@angular-eslint/eslint-plugin-template": { - "version": "18.4.3", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-18.4.3.tgz", - "integrity": "sha512-ijGlX2N01ayMXTpeQivOA31AszO8OEbu9ZQUCxnu9AyMMhxyi2q50bujRChAvN9YXQfdQtbxuajxV6+aiWb5BQ==", - "dev": true, - "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.4.3", - "@angular-eslint/utils": "18.4.3", - "aria-query": "5.3.2", - "axobject-query": "4.1.0" - }, - "peerDependencies": { - "@typescript-eslint/types": "^7.11.0 || ^8.0.0", - "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": "*" - } - }, - "node_modules/@angular-eslint/schematics": { - "version": "18.4.3", - "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-18.4.3.tgz", - "integrity": "sha512-D5maKn5e6n58+8n7jLFLD4g+RGPOPeDSsvPc1sqial5tEKLxAJQJS9WZ28oef3bhkob6C60D+1H0mMmEEVvyVA==", - "dev": true, - "dependencies": { - "@angular-devkit/core": ">= 18.0.0 < 19.0.0", - "@angular-devkit/schematics": ">= 18.0.0 < 19.0.0", - "@angular-eslint/eslint-plugin": "18.4.3", - "@angular-eslint/eslint-plugin-template": "18.4.3", - "ignore": "6.0.2", - "semver": "7.6.3", - "strip-json-comments": "3.1.1" - } - }, - "node_modules/@angular-eslint/template-parser": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-18.3.0.tgz", - "integrity": "sha512-1mUquqcnugI4qsoxcYZKZ6WMi6RPelDcJZg2YqGyuaIuhWmi3ZqJZLErSSpjP60+TbYZu7wM8Kchqa1bwJtEaQ==", - "dev": true, - "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.3.0", - "eslint-scope": "^8.0.2" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": "*" - } - }, - "node_modules/@angular-eslint/template-parser/node_modules/@angular-eslint/bundled-angular-compiler": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.3.0.tgz", - "integrity": "sha512-v/59FxUKnMzymVce99gV43huxoqXWMb85aKvzlNvLN+ScDu6ZE4YMiTQNpfapVL2lkxhs0uwB3jH17EYd5TcsA==", - "dev": true - }, - "node_modules/@angular-eslint/utils": { - "version": "18.4.3", - "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-18.4.3.tgz", - "integrity": "sha512-w0bJ9+ELAEiPBSTPPm9bvDngfu1d8JbzUhvs2vU+z7sIz/HMwUZT5S4naypj2kNN0gZYGYrW0lt+HIbW87zTAQ==", - "dev": true, - "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.4.3" - }, - "peerDependencies": { - "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": "*" - } - }, - "node_modules/@angular/animations": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.2.13.tgz", - "integrity": "sha512-rG5J5Ek5Hg+Tz2NjkNOaG6PupiNK/lPfophXpsR1t/nWujqnMWX2krahD/i6kgD+jNWNKCJCYSOVvCx/BHOtKA==", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0" - }, - "peerDependencies": { - "@angular/core": "18.2.13" - } - }, - "node_modules/@angular/build": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.14.tgz", - "integrity": "sha512-9g24Oe/ZLULacW3hEpRCjSZIJPJTzN5BeFbA27epSV5NsrQOoeUGsEpRs90Zmt6eReO0fW1BGshWRoZtpSedcw==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1802.14", - "@babel/core": "7.25.2", - "@babel/helper-annotate-as-pure": "7.24.7", - "@babel/helper-split-export-declaration": "7.24.7", - "@babel/plugin-syntax-import-attributes": "7.24.7", - "@inquirer/confirm": "3.1.22", - "@vitejs/plugin-basic-ssl": "1.1.0", - "browserslist": "^4.23.0", - "critters": "0.0.24", - "esbuild": "0.23.0", - "fast-glob": "3.3.2", - "https-proxy-agent": "7.0.5", - "listr2": "8.2.4", - "lmdb": "3.0.13", - "magic-string": "0.30.11", - "mrmime": "2.0.0", - "parse5-html-rewriting-stream": "7.0.0", - "picomatch": "4.0.2", - "piscina": "4.6.1", - "rollup": "4.22.4", - "sass": "1.77.6", - "semver": "7.6.3", - "vite": "5.4.14", - "watchpack": "2.4.1" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "@angular/compiler-cli": "^18.0.0", - "@angular/localize": "^18.0.0", - "@angular/platform-server": "^18.0.0", - "@angular/service-worker": "^18.0.0", - "less": "^4.2.0", - "postcss": "^8.4.0", - "tailwindcss": "^2.0.0 || ^3.0.0", - "typescript": ">=5.4 <5.6" - }, - "peerDependenciesMeta": { - "@angular/localize": { - "optional": true - }, - "@angular/platform-server": { - "optional": true - }, - "@angular/service-worker": { - "optional": true - }, - "less": { - "optional": true - }, - "postcss": { - "optional": true - }, - "tailwindcss": { - "optional": true - } - } - }, - "node_modules/@angular/cdk": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.14.tgz", - "integrity": "sha512-vDyOh1lwjfVk9OqoroZAP8pf3xxKUvyl+TVR8nJxL4c5fOfUFkD7l94HaanqKSRwJcI2xiztuu92IVoHn8T33Q==", - "dependencies": { - "tslib": "^2.3.0" - }, - "optionalDependencies": { - "parse5": "^7.1.2" - }, - "peerDependencies": { - "@angular/common": "^18.0.0 || ^19.0.0", - "@angular/core": "^18.0.0 || ^19.0.0", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, - "node_modules/@angular/cli": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.14.tgz", - "integrity": "sha512-kWgRRQtJPkr8iwN7DMbTi3sXOnv7H5QhbU/GgD3nNX3D8YCSPmnby4PAE/P3wn7FsIK9JsSchsCt7MZ37Urh9A==", - "dev": true, - "dependencies": { - "@angular-devkit/architect": "0.1802.14", - "@angular-devkit/core": "18.2.14", - "@angular-devkit/schematics": "18.2.14", - "@inquirer/prompts": "5.3.8", - "@listr2/prompt-adapter-inquirer": "2.0.15", - "@schematics/angular": "18.2.14", - "@yarnpkg/lockfile": "1.1.0", - "ini": "4.1.3", - "jsonc-parser": "3.3.1", - "listr2": "8.2.4", - "npm-package-arg": "11.0.3", - "npm-pick-manifest": "9.1.0", - "pacote": "18.0.6", - "resolve": "1.22.8", - "semver": "7.6.3", - "symbol-observable": "4.0.0", - "yargs": "17.7.2" - }, - "bin": { - "ng": "bin/ng.js" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular/common": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-18.2.13.tgz", - "integrity": "sha512-4ZqrNp1PoZo7VNvW+sbSc2CB2axP1sCH2wXl8B0wdjsj8JY1hF1OhuugwhpAHtGxqewed2kCXayE+ZJqSTV4jw==", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0" - }, - "peerDependencies": { - "@angular/core": "18.2.13", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, - "node_modules/@angular/compiler": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-18.2.13.tgz", - "integrity": "sha512-TzWcrkopyjFF+WeDr2cRe8CcHjU72KfYV3Sm2TkBkcXrkYX5sDjGWrBGrG3hRB4e4okqchrOCvm1MiTdy2vKMA==", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0" - }, - "peerDependencies": { - "@angular/core": "18.2.13" - }, - "peerDependenciesMeta": { - "@angular/core": { - "optional": true - } - } - }, - "node_modules/@angular/compiler-cli": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-18.2.13.tgz", - "integrity": "sha512-DBSh4AQwkiJDSiVvJATRmjxf6wyUs9pwQLgaFdSlfuTRO+sdb0J2z1r3BYm8t0IqdoyXzdZq2YCH43EmyvD71g==", - "dev": true, - "dependencies": { - "@babel/core": "7.25.2", - "@jridgewell/sourcemap-codec": "^1.4.14", - "chokidar": "^4.0.0", - "convert-source-map": "^1.5.1", - "reflect-metadata": "^0.2.0", - "semver": "^7.0.0", - "tslib": "^2.3.0", - "yargs": "^17.2.1" - }, - "bin": { - "ng-xi18n": "bundles/src/bin/ng_xi18n.js", - "ngc": "bundles/src/bin/ngc.js", - "ngcc": "bundles/ngcc/index.js" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0" - }, - "peerDependencies": { - "@angular/compiler": "18.2.13", - "typescript": ">=5.4 <5.6" - } - }, - "node_modules/@angular/core": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.13.tgz", - "integrity": "sha512-8mbWHMgO95OuFV1Ejy4oKmbe9NOJ3WazQf/f7wks8Bck7pcihd0IKhlPBNjFllbF5o+04EYSwFhEtvEgjMDClA==", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0" - }, - "peerDependencies": { - "rxjs": "^6.5.3 || ^7.4.0", - "zone.js": "~0.14.10" - } - }, - "node_modules/@angular/forms": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.2.13.tgz", - "integrity": "sha512-A67D867fu3DSBhdLWWZl/F5pr7v2+dRM2u3U7ZJ0ewh4a+sv+0yqWdJW+a8xIoiHxS+btGEJL2qAKJiH+MCFfg==", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0" - }, - "peerDependencies": { - "@angular/common": "18.2.13", - "@angular/core": "18.2.13", - "@angular/platform-browser": "18.2.13", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, - "node_modules/@angular/material": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-18.2.14.tgz", - "integrity": "sha512-28pxzJP49Mymt664WnCtPkKeg7kXUsQKTKGf/Kl95rNTEdTJLbnlcc8wV0rT0yQNR7kXgpfBnG7h0ETLv/iu5Q==", - "dependencies": { - "tslib": "^2.3.0" - }, - "peerDependencies": { - "@angular/animations": "^18.0.0 || ^19.0.0", - "@angular/cdk": "18.2.14", - "@angular/common": "^18.0.0 || ^19.0.0", - "@angular/core": "^18.0.0 || ^19.0.0", - "@angular/forms": "^18.0.0 || ^19.0.0", - "@angular/platform-browser": "^18.0.0 || ^19.0.0", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, - "node_modules/@angular/platform-browser": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.13.tgz", - "integrity": "sha512-tu7ZzY6qD3ATdWFzcTcsAKe7M6cJeWbT/4/bF9unyGO3XBPcNYDKoiz10+7ap2PUd0fmPwvuvTvSNJiFEBnB8Q==", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0" - }, - "peerDependencies": { - "@angular/animations": "18.2.13", - "@angular/common": "18.2.13", - "@angular/core": "18.2.13" - }, - "peerDependenciesMeta": { - "@angular/animations": { - "optional": true - } - } - }, - "node_modules/@angular/platform-browser-dynamic": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-18.2.13.tgz", - "integrity": "sha512-kbQCf9+8EpuJC7buBxhSiwBtXvjAwAKh6MznD6zd2pyCYqfY6gfRCZQRtK59IfgVtKmEONWI9grEyNIRoTmqJg==", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0" - }, - "peerDependencies": { - "@angular/common": "18.2.13", - "@angular/compiler": "18.2.13", - "@angular/core": "18.2.13", - "@angular/platform-browser": "18.2.13" - } - }, - "node_modules/@angular/router": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-18.2.13.tgz", - "integrity": "sha512-VKmfgi/r/CkyBq9nChQ/ptmfu0JT/8ONnLVJ5H+SkFLRYJcIRyHLKjRihMCyVm6xM5yktOdCaW73NTQrFz7+bg==", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0" - }, - "peerDependencies": { - "@angular/common": "18.2.13", - "@angular/core": "18.2.13", - "@angular/platform-browser": "18.2.13", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.5.tgz", - "integrity": "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", - "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.0", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-module-transforms": "^7.25.2", - "@babel/helpers": "^7.25.0", - "@babel/parser": "^7.25.0", - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.2", - "@babel/types": "^7.25.2", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", - "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.25.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", - "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", - "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz", - "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/traverse": "^7.25.9", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz", - "integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "regexpu-core": "^6.2.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", - "integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", - "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", - "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", - "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", - "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-wrap-function": "^7.25.9", - "@babel/traverse": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", - "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", - "dev": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/traverse": "^7.26.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", - "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", - "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", - "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", - "dev": true, - "dependencies": { - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz", - "integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==", - "dev": true, - "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.9.tgz", - "integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz", - "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.26.7" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", - "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", - "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", - "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", - "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", - "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", - "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", - "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.0.tgz", - "integrity": "sha512-uaIi2FdqzjpAMvVqvB51S42oC2JEVgh0LDsGfZVDysWE8LrJtQC2jvKmOqEYThKyB7bDEb7BP1GYWDm7tABA0Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-remap-async-to-generator": "^7.25.0", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/traverse": "^7.25.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", - "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-remap-async-to-generator": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz", - "integrity": "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", - "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", - "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", - "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", - "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9", - "@babel/traverse": "^7.25.9", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-classes/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", - "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/template": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", - "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", - "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", - "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", - "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", - "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", - "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", - "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", - "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", - "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", - "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", - "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", - "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", - "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", - "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", - "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", - "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", - "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.26.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz", - "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", - "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", - "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", - "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", - "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", - "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", - "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", - "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", - "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", - "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "regenerator-transform": "^0.15.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", - "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", - "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.1", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", - "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", - "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", - "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", - "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.26.7.tgz", - "integrity": "sha512-jfoTXXZTgGg36BmhqT3cAYK5qkmqvJpvNrPhaK/52Vgjhw4Rq29s9UqpWWV0D6yuRmgiFH/BUVlkl96zJWqnaw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", - "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", - "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", - "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", - "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.3.tgz", - "integrity": "sha512-QsYW7UeAaXvLPX9tdVliMJE7MD7M6MLYVTovRTIwhoYQVFHR1rM4wO8wqAezYi3/BpSD+NzVCZ69R6smWiIi8g==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.25.2", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-validator-option": "^7.24.8", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.3", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.24.7", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.24.7", - "@babel/plugin-transform-async-generator-functions": "^7.25.0", - "@babel/plugin-transform-async-to-generator": "^7.24.7", - "@babel/plugin-transform-block-scoped-functions": "^7.24.7", - "@babel/plugin-transform-block-scoping": "^7.25.0", - "@babel/plugin-transform-class-properties": "^7.24.7", - "@babel/plugin-transform-class-static-block": "^7.24.7", - "@babel/plugin-transform-classes": "^7.25.0", - "@babel/plugin-transform-computed-properties": "^7.24.7", - "@babel/plugin-transform-destructuring": "^7.24.8", - "@babel/plugin-transform-dotall-regex": "^7.24.7", - "@babel/plugin-transform-duplicate-keys": "^7.24.7", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0", - "@babel/plugin-transform-dynamic-import": "^7.24.7", - "@babel/plugin-transform-exponentiation-operator": "^7.24.7", - "@babel/plugin-transform-export-namespace-from": "^7.24.7", - "@babel/plugin-transform-for-of": "^7.24.7", - "@babel/plugin-transform-function-name": "^7.25.1", - "@babel/plugin-transform-json-strings": "^7.24.7", - "@babel/plugin-transform-literals": "^7.25.2", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", - "@babel/plugin-transform-member-expression-literals": "^7.24.7", - "@babel/plugin-transform-modules-amd": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/plugin-transform-modules-systemjs": "^7.25.0", - "@babel/plugin-transform-modules-umd": "^7.24.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", - "@babel/plugin-transform-new-target": "^7.24.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", - "@babel/plugin-transform-numeric-separator": "^7.24.7", - "@babel/plugin-transform-object-rest-spread": "^7.24.7", - "@babel/plugin-transform-object-super": "^7.24.7", - "@babel/plugin-transform-optional-catch-binding": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.8", - "@babel/plugin-transform-parameters": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.24.7", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-property-literals": "^7.24.7", - "@babel/plugin-transform-regenerator": "^7.24.7", - "@babel/plugin-transform-reserved-words": "^7.24.7", - "@babel/plugin-transform-shorthand-properties": "^7.24.7", - "@babel/plugin-transform-spread": "^7.24.7", - "@babel/plugin-transform-sticky-regex": "^7.24.7", - "@babel/plugin-transform-template-literals": "^7.24.7", - "@babel/plugin-transform-typeof-symbol": "^7.24.8", - "@babel/plugin-transform-unicode-escapes": "^7.24.7", - "@babel/plugin-transform-unicode-property-regex": "^7.24.7", - "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.4", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.37.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", - "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz", - "integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.5", - "@babel/parser": "^7.26.7", - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.7", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz", - "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.26.5", - "@babel/types": "^7.26.5", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/types": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz", - "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==", - "dev": true, - "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.1.tgz", - "integrity": "sha512-boghen8F0Q8D+0/Q1/1r6DUEieUJ8w2a1gIknExMSHBsJFOr2+0KUfHiVYBvucPwl3+RU5PFBK833FjFCh3BhA==", - "dev": true, - "engines": { - "node": ">=14.17.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz", - "integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.0.tgz", - "integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz", - "integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.0.tgz", - "integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz", - "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz", - "integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz", - "integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz", - "integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz", - "integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz", - "integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz", - "integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz", - "integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz", - "integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz", - "integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz", - "integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz", - "integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz", - "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz", - "integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz", - "integrity": "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz", - "integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz", - "integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz", - "integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz", - "integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz", - "integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true - }, - "node_modules/@inquirer/checkbox": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-2.5.0.tgz", - "integrity": "sha512-sMgdETOfi2dUHT8r7TT1BTKOwNvdDGFDXYWtQ2J69SvlYNntk9I/gJe7r5yvMwwsuKnYbuRs3pNhx4tgNck5aA==", - "dev": true, - "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/figures": "^1.0.5", - "@inquirer/type": "^1.5.3", - "ansi-escapes": "^4.3.2", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/confirm": { - "version": "3.1.22", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.1.22.tgz", - "integrity": "sha512-gsAKIOWBm2Q87CDfs9fEo7wJT3fwWIJfnDGMn9Qy74gBnNFOACDNfhUzovubbJjWnKLGBln7/NcSmZwj5DuEXg==", - "dev": true, - "dependencies": { - "@inquirer/core": "^9.0.10", - "@inquirer/type": "^1.5.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/core": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.2.1.tgz", - "integrity": "sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==", - "dev": true, - "dependencies": { - "@inquirer/figures": "^1.0.6", - "@inquirer/type": "^2.0.0", - "@types/mute-stream": "^0.0.4", - "@types/node": "^22.5.5", - "@types/wrap-ansi": "^3.0.0", - "ansi-escapes": "^4.3.2", - "cli-width": "^4.1.0", - "mute-stream": "^1.0.0", - "signal-exit": "^4.1.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/core/node_modules/@inquirer/type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-2.0.0.tgz", - "integrity": "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==", - "dev": true, - "dependencies": { - "mute-stream": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-2.2.0.tgz", - "integrity": "sha512-9KHOpJ+dIL5SZli8lJ6xdaYLPPzB8xB9GZItg39MBybzhxA16vxmszmQFrRwbOA918WA2rvu8xhDEg/p6LXKbw==", - "dev": true, - "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3", - "external-editor": "^3.1.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/expand": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-2.3.0.tgz", - "integrity": "sha512-qnJsUcOGCSG1e5DTOErmv2BPQqrtT6uzqn1vI/aYGiPKq+FgslGZmtdnXbhuI7IlT7OByDoEEqdnhUnVR2hhLw==", - "dev": true, - "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/figures": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.10.tgz", - "integrity": "sha512-Ey6176gZmeqZuY/W/nZiUyvmb1/qInjcpiZjXWi6nON+nxJpD1bxtSoBxNliGISae32n6OwbY+TSXPZ1CfS4bw==", - "dev": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/input": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-2.3.0.tgz", - "integrity": "sha512-XfnpCStx2xgh1LIRqPXrTNEEByqQWoxsWYzNRSEUxJ5c6EQlhMogJ3vHKu8aXuTacebtaZzMAHwEL0kAflKOBw==", - "dev": true, - "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/number": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-1.1.0.tgz", - "integrity": "sha512-ilUnia/GZUtfSZy3YEErXLJ2Sljo/mf9fiKc08n18DdwdmDbOzRcTv65H1jjDvlsAuvdFXf4Sa/aL7iw/NanVA==", - "dev": true, - "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/password": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-2.2.0.tgz", - "integrity": "sha512-5otqIpgsPYIshqhgtEwSspBQE40etouR8VIxzpJkv9i0dVHIpyhiivbkH9/dGiMLdyamT54YRdGJLfl8TFnLHg==", - "dev": true, - "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3", - "ansi-escapes": "^4.3.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/prompts": { - "version": "5.3.8", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-5.3.8.tgz", - "integrity": "sha512-b2BudQY/Si4Y2a0PdZZL6BeJtl8llgeZa7U2j47aaJSCeAl1e4UI7y8a9bSkO3o/ZbZrgT5muy/34JbsjfIWxA==", - "dev": true, - "dependencies": { - "@inquirer/checkbox": "^2.4.7", - "@inquirer/confirm": "^3.1.22", - "@inquirer/editor": "^2.1.22", - "@inquirer/expand": "^2.1.22", - "@inquirer/input": "^2.2.9", - "@inquirer/number": "^1.0.10", - "@inquirer/password": "^2.1.22", - "@inquirer/rawlist": "^2.2.4", - "@inquirer/search": "^1.0.7", - "@inquirer/select": "^2.4.7" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/rawlist": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-2.3.0.tgz", - "integrity": "sha512-zzfNuINhFF7OLAtGHfhwOW2TlYJyli7lOUoJUXw/uyklcwalV6WRXBXtFIicN8rTRK1XTiPWB4UY+YuW8dsnLQ==", - "dev": true, - "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/search": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-1.1.0.tgz", - "integrity": "sha512-h+/5LSj51dx7hp5xOn4QFnUaKeARwUCLs6mIhtkJ0JYPBLmEYjdHSYh7I6GrLg9LwpJ3xeX0FZgAG1q0QdCpVQ==", - "dev": true, - "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/figures": "^1.0.5", - "@inquirer/type": "^1.5.3", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/select": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-2.5.0.tgz", - "integrity": "sha512-YmDobTItPP3WcEI86GvPo+T2sRHkxxOq/kXmsBjHS5BVXUgvgZ5AfJjkvQvZr03T81NnI3KrrRuMzeuYUQRFOA==", - "dev": true, - "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/figures": "^1.0.5", - "@inquirer/type": "^1.5.3", - "ansi-escapes": "^4.3.2", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/type": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.5.5.tgz", - "integrity": "sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==", - "dev": true, - "dependencies": { - "mute-stream": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@jsonjoy.com/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", - "dev": true, - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/@jsonjoy.com/json-pack": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.1.tgz", - "integrity": "sha512-osjeBqMJ2lb/j/M8NCPjs1ylqWIcTRTycIhVB5pt6LgzgeRSb0YRZ7j9RfA8wIUrsr/medIuhVyonXRZWLyfdw==", - "dev": true, - "dependencies": { - "@jsonjoy.com/base64": "^1.1.1", - "@jsonjoy.com/util": "^1.1.2", - "hyperdyperid": "^1.2.0", - "thingies": "^1.20.0" - }, - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/@jsonjoy.com/util": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.5.0.tgz", - "integrity": "sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==", - "dev": true, - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", - "dev": true - }, - "node_modules/@listr2/prompt-adapter-inquirer": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-2.0.15.tgz", - "integrity": "sha512-MZrGem/Ujjd4cPTLYDfCZK2iKKeiO/8OX13S6jqxldLs0Prf2aGqVlJ77nMBqMv7fzqgXEgjrNHLXcKR8l9lOg==", - "dev": true, - "dependencies": { - "@inquirer/type": "^1.5.1" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "@inquirer/prompts": ">= 3 < 6" - } - }, - "node_modules/@lmdb/lmdb-darwin-arm64": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.0.13.tgz", - "integrity": "sha512-uiKPB0Fv6WEEOZjruu9a6wnW/8jrjzlZbxXscMB8kuCJ1k6kHpcBnuvaAWcqhbI7rqX5GKziwWEdD+wi2gNLfA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@lmdb/lmdb-darwin-x64": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.0.13.tgz", - "integrity": "sha512-bEVIIfK5mSQoG1R19qA+fJOvCB+0wVGGnXHT3smchBVahYBdlPn2OsZZKzlHWfb1E+PhLBmYfqB5zQXFP7hJig==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@lmdb/lmdb-linux-arm": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.0.13.tgz", - "integrity": "sha512-Yml1KlMzOnXj/tnW7yX8U78iAzTk39aILYvCPbqeewAq1kSzl+w59k/fiVkTBfvDi/oW/5YRxL+Fq+Y1Fr1r2Q==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@lmdb/lmdb-linux-arm64": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.0.13.tgz", - "integrity": "sha512-afbVrsMgZ9dUTNUchFpj5VkmJRxvht/u335jUJ7o23YTbNbnpmXif3VKQGCtnjSh+CZaqm6N3CPG8KO3zwyZ1Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@lmdb/lmdb-linux-x64": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.0.13.tgz", - "integrity": "sha512-vOtxu0xC0SLdQ2WRXg8Qgd8T32ak4SPqk5zjItRszrJk2BdeXqfGxBJbP7o4aOvSPSmSSv46Lr1EP4HXU8v7Kg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@lmdb/lmdb-win32-x64": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.0.13.tgz", - "integrity": "sha512-UCrMJQY/gJnOl3XgbWRZZUvGGBuKy6i0YNSptgMzHBjs+QYDYR1Mt/RLTOPy4fzzves65O1EDmlL//OzEqoLlA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", - "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", - "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", - "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", - "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", - "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", - "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@ngrx/component-store": { - "version": "18.1.1", - "resolved": "https://registry.npmjs.org/@ngrx/component-store/-/component-store-18.1.1.tgz", - "integrity": "sha512-+FDd44D+unx/eE/7qCyK1IDsTu8503JOnj3lEhL9f9TDmE4arDNqeNx/8Sh3V3HDkH7mVC9iV0c538ACGlvWIA==", - "dependencies": { - "tslib": "^2.0.0" - }, - "peerDependencies": { - "@angular/core": "^18.0.0", - "rxjs": "^6.5.3 || ^7.5.0" - } - }, - "node_modules/@ngrx/effects": { - "version": "18.1.1", - "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-18.1.1.tgz", - "integrity": "sha512-XXob8kYEvYMaZwgHtrrTW0XZargbu5PloEpNHLnzB8jPk0yWEw6keryxaF09Ylws1779MWvMmF/YP2rPl04nHQ==", - "dependencies": { - "tslib": "^2.0.0" - }, - "peerDependencies": { - "@angular/core": "^18.0.0", - "@ngrx/store": "18.1.1", - "rxjs": "^6.5.3 || ^7.5.0" - } - }, - "node_modules/@ngrx/store": { - "version": "18.1.1", - "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-18.1.1.tgz", - "integrity": "sha512-K0v1akJ2sEnIeb1AUA064+ksgRgbMgVG9HbSsLBxENbFjK2ZvKRxo1bpOw6WHW9+hyDTlhZGl7+gUtjmo3497g==", - "dependencies": { - "tslib": "^2.0.0" - }, - "peerDependencies": { - "@angular/core": "^18.0.0", - "rxjs": "^6.5.3 || ^7.5.0" - } - }, - "node_modules/@ngtools/webpack": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.14.tgz", - "integrity": "sha512-rT+Y4WR8QTVsijtb+YRqHcPTpd1ZiwRbklQXRTxU0YGFHpxpi+bhjmY8FjpPoAtdPO1Lg3l3KIZPZa0thG0FNg==", - "dev": true, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "@angular/compiler-cli": "^18.0.0", - "typescript": ">=5.4 <5.6", - "webpack": "^5.54.0" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@npmcli/agent": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", - "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", - "socks-proxy-agent": "^8.0.3" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/agent/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true - }, - "node_modules/@npmcli/fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", - "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", - "dev": true, - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/git": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", - "integrity": "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==", - "dev": true, - "dependencies": { - "@npmcli/promise-spawn": "^7.0.0", - "ini": "^4.1.3", - "lru-cache": "^10.0.1", - "npm-pick-manifest": "^9.0.0", - "proc-log": "^4.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^2.0.1", - "semver": "^7.3.5", - "which": "^4.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/git/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "dev": true, - "engines": { - "node": ">=16" - } - }, - "node_modules/@npmcli/git/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true - }, - "node_modules/@npmcli/git/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", - "dev": true, - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/installed-package-contents": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", - "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", - "dev": true, - "dependencies": { - "npm-bundled": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "bin": { - "installed-package-contents": "bin/index.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/node-gyp": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", - "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/package-json": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.2.1.tgz", - "integrity": "sha512-f7zYC6kQautXHvNbLEWgD/uGu1+xCn9izgqBfgItWSx22U0ZDekxN08A1vM8cTxj/cRVe0Q94Ode+tdoYmIOOQ==", - "dev": true, - "dependencies": { - "@npmcli/git": "^5.0.0", - "glob": "^10.2.2", - "hosted-git-info": "^7.0.0", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "proc-log": "^4.0.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/package-json/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@npmcli/promise-spawn": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", - "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", - "dev": true, - "dependencies": { - "which": "^4.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/promise-spawn/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "dev": true, - "engines": { - "node": ">=16" - } - }, - "node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", - "dev": true, - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/redact": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-2.0.1.tgz", - "integrity": "sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw==", - "dev": true, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/run-script": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-8.1.0.tgz", - "integrity": "sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==", - "dev": true, - "dependencies": { - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/package-json": "^5.0.0", - "@npmcli/promise-spawn": "^7.0.0", - "node-gyp": "^10.0.0", - "proc-log": "^4.0.0", - "which": "^4.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/run-script/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "dev": true, - "engines": { - "node": ">=16" - } - }, - "node_modules/@npmcli/run-script/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", - "dev": true, - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^16.13.0 || >=18.0.0" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@pkgr/core": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", - "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", - "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", - "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", - "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", - "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", - "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", - "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", - "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", - "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", - "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", - "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", - "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", - "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", - "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", - "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", - "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", - "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@schematics/angular": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.14.tgz", - "integrity": "sha512-CHh6ew2Az71UlvVcnYeuMEwjwkZqR7y/9ebLzFRvczC71ZL8qPVBpBTVGbCpGBd54VEbCZVWRxBQoZZ5LP/aBw==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "18.2.14", - "@angular-devkit/schematics": "18.2.14", - "jsonc-parser": "3.3.1" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@sigstore/bundle": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", - "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", - "dev": true, - "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@sigstore/core": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.1.0.tgz", - "integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==", - "dev": true, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@sigstore/protobuf-specs": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.3.tgz", - "integrity": "sha512-RpacQhBlwpBWd7KEJsRKcBQalbV28fvkxwTOJIqhIuDysMMaJW47V4OqW30iJB9uRpqOSxxEAQFdr8tTattReQ==", - "dev": true, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@sigstore/sign": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.2.tgz", - "integrity": "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==", - "dev": true, - "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "make-fetch-happen": "^13.0.1", - "proc-log": "^4.2.0", - "promise-retry": "^2.0.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@sigstore/tuf": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.4.tgz", - "integrity": "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==", - "dev": true, - "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2", - "tuf-js": "^2.2.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@sigstore/verify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.1.tgz", - "integrity": "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==", - "dev": true, - "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.1.0", - "@sigstore/protobuf-specs": "^0.3.2" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@sindresorhus/merge-streams": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", - "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", - "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", - "dev": true - }, - "node_modules/@tufjs/canonical-json": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", - "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", - "dev": true, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@tufjs/models": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.1.tgz", - "integrity": "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==", - "dev": true, - "dependencies": { - "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.5", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", - "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", - "dev": true, - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/bonjour": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", - "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", - "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", - "dev": true, - "dependencies": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, - "node_modules/@types/cors": { - "version": "2.8.17", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", - "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", - "dev": true - }, - "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true - }, - "node_modules/@types/express": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", - "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", - "dev": true, - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", - "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/express/node_modules/@types/express-serve-static-core": { - "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", - "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/http-errors": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", - "dev": true - }, - "node_modules/@types/http-proxy": { - "version": "1.17.15", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.15.tgz", - "integrity": "sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/jasmine": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.3.6.tgz", - "integrity": "sha512-3N0FpQTeiWjm+Oo1WUYWguUS7E6JLceiGTriFrG8k5PU7zRLJCzLcWURU3wjMbZGS//a2/LgjsnO3QxIlwxt9g==", - "dev": true - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true - }, - "node_modules/@types/mute-stream": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", - "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/node": { - "version": "22.12.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.12.0.tgz", - "integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==", - "dev": true, - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "node_modules/@types/node-forge": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", - "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/qs": { - "version": "6.9.18", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", - "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", - "dev": true - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true - }, - "node_modules/@types/retry": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", - "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", - "dev": true - }, - "node_modules/@types/send": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", - "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", - "dev": true, - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-index": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", - "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", - "dev": true, - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.7", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", - "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", - "dev": true, - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "*" - } - }, - "node_modules/@types/sockjs": { - "version": "0.3.36", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", - "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/wrap-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", - "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", - "dev": true - }, - "node_modules/@types/ws": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.14.tgz", - "integrity": "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.22.0.tgz", - "integrity": "sha512-4Uta6REnz/xEJMvwf72wdUnC3rr4jAQf5jnTkeRQ9b6soxLxhDEbS/pfMPoJLDfFPNVRdryqWUIV/2GZzDJFZw==", - "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.22.0", - "@typescript-eslint/type-utils": "8.22.0", - "@typescript-eslint/utils": "8.22.0", - "@typescript-eslint/visitor-keys": "8.22.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/experimental-utils": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz", - "integrity": "sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.3", - "@typescript-eslint/types": "3.10.1", - "@typescript-eslint/typescript-estree": "3.10.1", - "eslint-scope": "^5.0.0", - "eslint-utils": "^2.0.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - } - }, - "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/types": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", - "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==", - "dev": true, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", - "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "3.10.1", - "@typescript-eslint/visitor-keys": "3.10.1", - "debug": "^4.1.1", - "glob": "^7.1.6", - "is-glob": "^4.0.1", - "lodash": "^4.17.15", - "semver": "^7.3.2", - "tsutils": "^3.17.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", - "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@typescript-eslint/experimental-utils/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.22.0.tgz", - "integrity": "sha512-MqtmbdNEdoNxTPzpWiWnqNac54h8JDAmkWtJExBVVnSrSmi9z+sZUt0LfKqk9rjqmKOIeRhO4fHHJ1nQIjduIQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "8.22.0", - "@typescript-eslint/types": "8.22.0", - "@typescript-eslint/typescript-estree": "8.22.0", - "@typescript-eslint/visitor-keys": "8.22.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.22.0.tgz", - "integrity": "sha512-/lwVV0UYgkj7wPSw0o8URy6YI64QmcOdwHuGuxWIYznO6d45ER0wXUbksr9pYdViAofpUCNJx/tAzNukgvaaiQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.22.0", - "@typescript-eslint/visitor-keys": "8.22.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.22.0.tgz", - "integrity": "sha512-NzE3aB62fDEaGjaAYZE4LH7I1MUwHooQ98Byq0G0y3kkibPJQIXVUspzlFOmOfHhiDLwKzMlWxaNv+/qcZurJA==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "8.22.0", - "@typescript-eslint/utils": "8.22.0", - "debug": "^4.3.4", - "ts-api-utils": "^2.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.22.0.tgz", - "integrity": "sha512-0S4M4baNzp612zwpD4YOieP3VowOARgK2EkN/GBn95hpyF8E2fbMT55sRHWBq+Huaqk3b3XK+rxxlM8sPgGM6A==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.22.0.tgz", - "integrity": "sha512-SJX99NAS2ugGOzpyhMza/tX+zDwjvwAtQFLsBo3GQxiGcvaKlqGBkmZ+Y1IdiSi9h4Q0Lr5ey+Cp9CGWNY/F/w==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.22.0", - "@typescript-eslint/visitor-keys": "8.22.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.8.0" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.22.0.tgz", - "integrity": "sha512-T8oc1MbF8L+Bk2msAvCUzjxVB2Z2f+vXYfcucE2wOmYs7ZUwco5Ep0fYZw8quNwOiw9K8GYVL+Kgc2pETNTLOg==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.22.0", - "@typescript-eslint/types": "8.22.0", - "@typescript-eslint/typescript-estree": "8.22.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.22.0.tgz", - "integrity": "sha512-AWpYAXnUgvLNabGTy3uBylkgZoosva/miNd1I8Bz3SjotmQPbVqhO4Cczo8AsZ44XVErEBPr/CRSgaj8sG7g0w==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.22.0", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true - }, - "node_modules/@vitejs/plugin-basic-ssl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz", - "integrity": "sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A==", - "dev": true, - "engines": { - "node": ">=14.6.0" - }, - "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "dev": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "dev": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "dev": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "dev": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "dev": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "node_modules/@yarnpkg/lockfile": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", - "dev": true - }, - "node_modules/abbrev": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", - "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "dev": true, - "peerDependencies": { - "acorn": "^8" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/adjust-sourcemap-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", - "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", - "dev": true, - "dependencies": { - "loader-utils": "^2.0.0", - "regex-parser": "^2.2.11" - }, - "engines": { - "node": ">=8.9" - } - }, - "node_modules/adjust-sourcemap-loader/node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "dev": true, - "engines": { - "node": ">= 14" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dev": true, - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-html-community": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", - "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", - "dev": true, - "engines": [ - "node >= 0.8.0" - ], - "bin": { - "ansi-html": "bin/ansi-html" - } - }, - "node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.20", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", - "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "browserslist": "^4.23.3", - "caniuse-lite": "^1.0.30001646", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/babel-loader": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", - "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==", - "dev": true, - "dependencies": { - "find-cache-dir": "^4.0.0", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 14.15.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0", - "webpack": ">=5" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.12", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz", - "integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.3", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", - "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2", - "core-js-compat": "^3.38.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz", - "integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.3" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "dev": true, - "engines": { - "node": "^4.5.0 || >= 5.9" - } - }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", - "dev": true - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/bonjour-service": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", - "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "node_modules/bundle-name": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", - "dev": true, - "dependencies": { - "run-applescript": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cacache": { - "version": "18.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", - "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", - "dev": true, - "dependencies": { - "@npmcli/fs": "^3.1.0", - "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", - "minipass": "^7.0.3", - "minipass-collect": "^2.0.1", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/cacache/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", - "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", - "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", - "dev": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001696", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz", - "integrity": "sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "dev": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", - "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", - "dev": true, - "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/clone-deep/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/commist": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/commist/-/commist-1.1.0.tgz", - "integrity": "sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==", - "dependencies": { - "leven": "^2.1.0", - "minimist": "^1.1.0" - } - }, - "node_modules/common-path-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", - "dev": true - }, - "node_modules/common-tags": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.5.tgz", - "integrity": "sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "negotiator": "~0.6.4", - "on-headers": "~1.0.2", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "engines": [ - "node >= 6.0" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/connect-history-api-fallback": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", - "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/connect/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/connect/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true - }, - "node_modules/copy-anything": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", - "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", - "dev": true, - "dependencies": { - "is-what": "^3.14.1" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/copy-webpack-plugin": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", - "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", - "dev": true, - "dependencies": { - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.1", - "globby": "^14.0.0", - "normalize-path": "^3.0.0", - "schema-utils": "^4.2.0", - "serialize-javascript": "^6.0.2" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - } - }, - "node_modules/core-js-compat": { - "version": "3.40.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.40.0.tgz", - "integrity": "sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==", - "dev": true, - "dependencies": { - "browserslist": "^4.24.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dev": true, - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", - "dev": true, - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/critters": { - "version": "0.0.24", - "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.24.tgz", - "integrity": "sha512-Oyqew0FGM0wYUSNqR0L6AteO5MpMoUU0rhKRieXeiKs+PmRTxiJMyaunYB2KF6fQ3dzChXKCpbFOEJx3OQ1v/Q==", - "deprecated": "Ownership of Critters has moved to the Nuxt team, who will be maintaining the project going forward. If you'd like to keep using Critters, please switch to the actively-maintained fork at https://github.com/danielroe/beasties", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "css-select": "^5.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.2", - "htmlparser2": "^8.0.2", - "postcss": "^8.4.23", - "postcss-media-query-parser": "^0.2.3" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-loader": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", - "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", - "dev": true, - "dependencies": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.1.0", - "postcss-modules-local-by-default": "^4.0.5", - "postcss-modules-scope": "^3.2.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.27.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", - "dev": true - }, - "node_modules/date-format": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", - "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/default-browser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", - "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", - "dev": true, - "dependencies": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", - "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-gateway": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", - "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", - "dev": true, - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "dev": true, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true - }, - "node_modules/di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", - "dev": true - }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true - }, - "node_modules/dns-packet": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", - "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", - "dev": true, - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", - "dev": true, - "dependencies": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "dev": true, - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/duplexify": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", - "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", - "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.2" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true - }, - "node_modules/electron-to-chromium": { - "version": "1.5.90", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.90.tgz", - "integrity": "sha512-C3PN4aydfW91Natdyd449Kw+BzhLmof6tzy5W1pFC5SpQxVXT+oyiyOG9AgYYSN9OdA/ik3YkCrpwqI8ug5Tug==", - "dev": true - }, - "node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/engine.io": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", - "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", - "dev": true, - "dependencies": { - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.7.2", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.17.1" - }, - "engines": { - "node": ">=10.2.0" - } - }, - "node_modules/engine.io-parser": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", - "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/engine.io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/engine.io/node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/enhanced-resolve": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", - "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/enquirer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", - "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/ent": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.2.tgz", - "integrity": "sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "punycode": "^1.4.1", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "devOptional": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true - }, - "node_modules/errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dev": true, - "optional": true, - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", - "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", - "dev": true - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", - "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.23.0", - "@esbuild/android-arm": "0.23.0", - "@esbuild/android-arm64": "0.23.0", - "@esbuild/android-x64": "0.23.0", - "@esbuild/darwin-arm64": "0.23.0", - "@esbuild/darwin-x64": "0.23.0", - "@esbuild/freebsd-arm64": "0.23.0", - "@esbuild/freebsd-x64": "0.23.0", - "@esbuild/linux-arm": "0.23.0", - "@esbuild/linux-arm64": "0.23.0", - "@esbuild/linux-ia32": "0.23.0", - "@esbuild/linux-loong64": "0.23.0", - "@esbuild/linux-mips64el": "0.23.0", - "@esbuild/linux-ppc64": "0.23.0", - "@esbuild/linux-riscv64": "0.23.0", - "@esbuild/linux-s390x": "0.23.0", - "@esbuild/linux-x64": "0.23.0", - "@esbuild/netbsd-x64": "0.23.0", - "@esbuild/openbsd-arm64": "0.23.0", - "@esbuild/openbsd-x64": "0.23.0", - "@esbuild/sunos-x64": "0.23.0", - "@esbuild/win32-arm64": "0.23.0", - "@esbuild/win32-ia32": "0.23.0", - "@esbuild/win32-x64": "0.23.0" - } - }, - "node_modules/esbuild-wasm": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.23.0.tgz", - "integrity": "sha512-6jP8UmWy6R6TUUV8bMuC3ZyZ6lZKI56x0tkxyCIqWwRRJ/DgeQKneh/Oid5EoGoPFLrGNkz47ZEtWAYuiY/u9g==", - "dev": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-prettier": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", - "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", - "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-prettier": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.3.tgz", - "integrity": "sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw==", - "dev": true, - "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.9.1" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-plugin-prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "eslint-config-prettier": "*", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/execa/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/execa/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/exponential-backoff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", - "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", - "dev": true - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "dev": true, - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/express/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ] - }, - "node_modules/fastq": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", - "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dev": true, - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/finalhandler/node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-cache-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", - "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", - "dev": true, - "dependencies": { - "common-path-prefix": "^3.0.0", - "pkg-dir": "^7.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", - "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", - "dev": true - }, - "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/fs-minipass": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", - "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", - "dev": true, - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", - "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", - "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", - "dev": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "function-bind": "^1.1.2", - "get-proto": "^1.0.0", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/globby": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", - "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", - "dev": true, - "dependencies": { - "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.2", - "ignore": "^5.2.4", - "path-type": "^5.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "dev": true - }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/help-me": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/help-me/-/help-me-3.0.0.tgz", - "integrity": "sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ==", - "dependencies": { - "glob": "^7.1.6", - "readable-stream": "^3.6.0" - } - }, - "node_modules/hosted-git-info": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", - "dev": true, - "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true - }, - "node_modules/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/hpack.js/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/hpack.js/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/hpack.js/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/html-entities": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", - "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } - ] - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true - }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", - "dev": true - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-parser-js": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.9.tgz", - "integrity": "sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw==", - "dev": true - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/http-proxy-middleware": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.3.tgz", - "integrity": "sha512-usY0HG5nyDUwtqpiZdETNbmKtw3QQ1jwYFZ9wi5iHzX2BcILwQKtYDJPo7XHTsu5Z0B2Hj3W9NNnbd+AjFWjqg==", - "dev": true, - "dependencies": { - "@types/http-proxy": "^1.17.15", - "debug": "^4.3.6", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.3", - "is-plain-object": "^5.0.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", - "dev": true, - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/hyperdyperid": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", - "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", - "dev": true, - "engines": { - "node": ">=10.18" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz", - "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/ignore-walk": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", - "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", - "dev": true, - "dependencies": { - "minimatch": "^9.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", - "dev": true, - "optional": true, - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/immutable": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", - "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", - "dev": true - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", - "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", - "dev": true, - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, - "engines": { - "node": ">= 12" - } - }, - "node_modules/ipaddr.js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", - "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "dev": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dev": true, - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "dev": true - }, - "node_modules/is-network-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", - "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-what": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", - "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", - "dev": true - }, - "node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", - "dev": true, - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/isbinaryfile": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", - "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", - "dev": true, - "engines": { - "node": ">= 8.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/gjtorikian/" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jasmine-core": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.1.tgz", - "integrity": "sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==", - "dev": true - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jiti": { - "version": "1.21.7", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", - "dev": true, - "bin": { - "jiti": "bin/jiti.js" - } - }, - "node_modules/js-sdsl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", - "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "dev": true - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "node_modules/json-parse-even-better-errors": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", - "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "dev": true - }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", - "dev": true, - "engines": [ - "node >= 0.2.0" - ] - }, - "node_modules/karma": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", - "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", - "dev": true, - "dependencies": { - "@colors/colors": "1.5.0", - "body-parser": "^1.19.0", - "braces": "^3.0.2", - "chokidar": "^3.5.1", - "connect": "^3.7.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.1", - "glob": "^7.1.7", - "graceful-fs": "^4.2.6", - "http-proxy": "^1.18.1", - "isbinaryfile": "^4.0.8", - "lodash": "^4.17.21", - "log4js": "^6.4.1", - "mime": "^2.5.2", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.5", - "qjobs": "^1.2.0", - "range-parser": "^1.2.1", - "rimraf": "^3.0.2", - "socket.io": "^4.7.2", - "source-map": "^0.6.1", - "tmp": "^0.2.1", - "ua-parser-js": "^0.7.30", - "yargs": "^16.1.1" - }, - "bin": { - "karma": "bin/karma" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/karma-chrome-launcher": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz", - "integrity": "sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q==", - "dev": true, - "dependencies": { - "which": "^1.2.1" - } - }, - "node_modules/karma-chrome-launcher/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/karma-coverage": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.1.tgz", - "integrity": "sha512-yj7hbequkQP2qOSb20GuNSIyE//PgJWHwC2IydLE6XRtsnaflv+/OSGNssPjobYUlhVVagy99TQpqUt3vAUG7A==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.2.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.1", - "istanbul-reports": "^3.0.5", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/karma-coverage/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/karma-coverage/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/karma-coverage/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/karma-coverage/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/karma-jasmine": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.1.0.tgz", - "integrity": "sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ==", - "dev": true, - "dependencies": { - "jasmine-core": "^4.1.0" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "karma": "^6.0.0" - } - }, - "node_modules/karma-jasmine-html-reporter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.1.0.tgz", - "integrity": "sha512-sPQE1+nlsn6Hwb5t+HHwyy0A1FNCVKuL1192b+XNauMYWThz2kweiBVW1DqloRpVvZIJkIoHVB7XRpK78n1xbQ==", - "dev": true, - "peerDependencies": { - "jasmine-core": "^4.0.0 || ^5.0.0", - "karma": "^6.0.0", - "karma-jasmine": "^5.0.0" - } - }, - "node_modules/karma-source-map-support": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", - "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", - "dev": true, - "dependencies": { - "source-map-support": "^0.5.5" - } - }, - "node_modules/karma/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/karma/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/karma/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/karma/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/karma/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/karma/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/karma/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/karma/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/karma/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/karma/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/karma/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/karma/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/karma/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/karma/node_modules/tmp": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", - "dev": true, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/karma/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/karma/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/karma/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/launch-editor": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.9.1.tgz", - "integrity": "sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==", - "dev": true, - "dependencies": { - "picocolors": "^1.0.0", - "shell-quote": "^1.8.1" - } - }, - "node_modules/less": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz", - "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", - "dev": true, - "dependencies": { - "copy-anything": "^2.0.1", - "parse-node-version": "^1.0.1", - "tslib": "^2.3.0" - }, - "bin": { - "lessc": "bin/lessc" - }, - "engines": { - "node": ">=6" - }, - "optionalDependencies": { - "errno": "^0.1.1", - "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "make-dir": "^2.1.0", - "mime": "^1.4.1", - "needle": "^3.1.0", - "source-map": "~0.6.0" - } - }, - "node_modules/less-loader": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-12.2.0.tgz", - "integrity": "sha512-MYUxjSQSBUQmowc0l5nPieOYwMzGPUaTzB6inNW/bdPEG9zOL3eAAD1Qw5ZxSPk7we5dMojHwNODYMV1hq4EVg==", - "dev": true, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "less": "^3.5.0 || ^4.0.0", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/less/node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "optional": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/less/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "optional": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/less/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "optional": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/less/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/license-webpack-plugin": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", - "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", - "dev": true, - "dependencies": { - "webpack-sources": "^3.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - }, - "webpack-sources": { - "optional": true - } - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/listr2": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.4.tgz", - "integrity": "sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==", - "dev": true, - "dependencies": { - "cli-truncate": "^4.0.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^6.1.0", - "rfdc": "^1.4.1", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/listr2/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/listr2/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/listr2/node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "dev": true - }, - "node_modules/listr2/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/listr2/node_modules/wrap-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", - "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/lmdb": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.0.13.tgz", - "integrity": "sha512-UGe+BbaSUQtAMZobTb4nHvFMrmvuAQKSeaqAX2meTEQjfsbpl5sxdHD8T72OnwD4GU9uwNhYXIVe4QGs8N9Zyw==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "msgpackr": "^1.10.2", - "node-addon-api": "^6.1.0", - "node-gyp-build-optional-packages": "5.2.2", - "ordered-binary": "^1.4.1", - "weak-lru-cache": "^1.2.2" - }, - "bin": { - "download-lmdb-prebuilds": "bin/download-prebuilds.js" - }, - "optionalDependencies": { - "@lmdb/lmdb-darwin-arm64": "3.0.13", - "@lmdb/lmdb-darwin-x64": "3.0.13", - "@lmdb/lmdb-linux-arm": "3.0.13", - "@lmdb/lmdb-linux-arm64": "3.0.13", - "@lmdb/lmdb-linux-x64": "3.0.13", - "@lmdb/lmdb-win32-x64": "3.0.13" - } - }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/loader-utils": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", - "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", - "dev": true, - "engines": { - "node": ">= 12.13.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", - "dev": true, - "dependencies": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/ansi-escapes": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", - "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", - "dev": true, - "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/log-update/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-update/node_modules/is-fullwidth-code-point": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", - "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", - "dev": true, - "dependencies": { - "get-east-asian-width": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", - "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", - "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", - "dev": true, - "dependencies": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/ansi-escapes": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", - "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", - "dev": true, - "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/log-update/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-update/node_modules/is-fullwidth-code-point": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", - "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", - "dev": true, - "dependencies": { - "get-east-asian-width": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", - "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", - "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/log4js": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", - "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", - "dev": true, - "dependencies": { - "date-format": "^4.0.14", - "debug": "^4.3.4", - "flatted": "^3.2.7", - "rfdc": "^1.3.0", - "streamroller": "^3.1.5" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/loglevel": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", - "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", - "dev": true, - "engines": { - "node": ">= 0.6.0" - }, - "funding": { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/loglevel" - } - }, - "node_modules/loglevel-colored-level-prefix": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz", - "integrity": "sha512-u45Wcxxc+SdAlh4yeF/uKlC1SPUPCy0gullSNKXod5I4bmifzk+Q4lSLExNEVn19tGaJipbZ4V4jbFn79/6mVA==", - "dev": true, - "dependencies": { - "chalk": "^1.1.3", - "loglevel": "^1.4.1" - } - }, - "node_modules/loglevel-colored-level-prefix/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loglevel-colored-level-prefix/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loglevel-colored-level-prefix/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loglevel-colored-level-prefix/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/loglevel-colored-level-prefix/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loglevel-colored-level-prefix/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/magic-string": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-fetch-happen": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", - "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", - "dev": true, - "dependencies": { - "@npmcli/agent": "^2.0.0", - "cacache": "^18.0.0", - "http-cache-semantics": "^4.1.1", - "is-lambda": "^1.0.1", - "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "proc-log": "^4.2.0", - "promise-retry": "^2.0.1", - "ssri": "^10.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memfs": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.0.tgz", - "integrity": "sha512-4eirfZ7thblFmqFjywlTmuWVSvccHAJbn1r8qQLzmTO11qcqpohOjmY2mFce6x7x7WtskzRqApPD0hv+Oa74jg==", - "dev": true, - "dependencies": { - "@jsonjoy.com/json-pack": "^1.0.3", - "@jsonjoy.com/util": "^1.3.0", - "tree-dump": "^1.0.1", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">= 4.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mini-css-extract-plugin": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", - "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", - "dev": true, - "dependencies": { - "schema-utils": "^4.0.0", - "tapable": "^2.2.1" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minipass-collect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", - "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", - "dev": true, - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minipass-fetch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", - "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", - "dev": true, - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-flush/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mqtt": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-4.3.7.tgz", - "integrity": "sha512-ew3qwG/TJRorTz47eW46vZ5oBw5MEYbQZVaEji44j5lAUSQSqIEoul7Kua/BatBW0H0kKQcC9kwUHa1qzaWHSw==", - "dependencies": { - "commist": "^1.0.0", - "concat-stream": "^2.0.0", - "debug": "^4.1.1", - "duplexify": "^4.1.1", - "help-me": "^3.0.0", - "inherits": "^2.0.3", - "lru-cache": "^6.0.0", - "minimist": "^1.2.5", - "mqtt-packet": "^6.8.0", - "number-allocator": "^1.0.9", - "pump": "^3.0.0", - "readable-stream": "^3.6.0", - "reinterval": "^1.1.0", - "rfdc": "^1.3.0", - "split2": "^3.1.0", - "ws": "^7.5.5", - "xtend": "^4.0.2" - }, - "bin": { - "mqtt": "bin/mqtt.js", - "mqtt_pub": "bin/pub.js", - "mqtt_sub": "bin/sub.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/mqtt-browser": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/mqtt-browser/-/mqtt-browser-4.3.7.tgz", - "integrity": "sha512-4pxHxa3avIILr2CXhTKlArVpATqfyTu4zr5u2PoUwzgw0GDr5dpzZ0pmPgZyOoQBVgrVDEboCzb/b1Q0yWOm7g==", - "dependencies": { - "mqtt": "4.3.7" - } - }, - "node_modules/mqtt-packet": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-6.10.0.tgz", - "integrity": "sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA==", - "dependencies": { - "bl": "^4.0.2", - "debug": "^4.1.1", - "process-nextick-args": "^2.0.1" - } - }, - "node_modules/mqtt/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mqtt/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/mrmime": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", - "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/msgpackr": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.2.tgz", - "integrity": "sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==", - "dev": true, - "optionalDependencies": { - "msgpackr-extract": "^3.0.2" - } - }, - "node_modules/msgpackr-extract": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", - "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "dependencies": { - "node-gyp-build-optional-packages": "5.2.2" - }, - "bin": { - "download-msgpackr-prebuilds": "bin/download-prebuilds.js" - }, - "optionalDependencies": { - "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" - } - }, - "node_modules/multicast-dns": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", - "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", - "dev": true, - "dependencies": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" - } - }, - "node_modules/mute-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", - "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/needle": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", - "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", - "dev": true, - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.3", - "sax": "^1.2.4" - }, - "bin": { - "needle": "bin/needle" - }, - "engines": { - "node": ">= 4.4.x" - } - }, - "node_modules/needle/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/ngx-mask": { - "version": "16.4.2", - "resolved": "https://registry.npmjs.org/ngx-mask/-/ngx-mask-16.4.2.tgz", - "integrity": "sha512-mQjcsTpctGu6HYKLf6/gjEUvW65D+46xvPIMYz0BDZXqHXrqKVluHXR3KF++TNOfdLLXwW6SvuHWd91NZN/C1A==", - "dependencies": { - "tslib": "^2.3.0" - }, - "peerDependencies": { - "@angular/common": ">=14.0.0", - "@angular/core": ">=14.0.0", - "@angular/forms": ">=14.0.0" - } - }, - "node_modules/ngx-mqtt": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/ngx-mqtt/-/ngx-mqtt-17.0.0.tgz", - "integrity": "sha512-54wVMyDOZkpTZEs0rTMWPP1Yz+6q3rRnHzIBnpqnBkDcyMfNrti45C7ijwnEIaPDzQHMOqVrDgh/6C4ocPPLJQ==", - "dependencies": { - "mqtt-browser": "4.3.7", - "mqtt-packet": "^6.10.0", - "tslib": "^2.3.0" - }, - "peerDependencies": { - "@angular/common": ">=14", - "@angular/core": ">=14" - } - }, - "node_modules/nice-napi": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", - "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "!win32" - ], - "dependencies": { - "node-addon-api": "^3.0.0", - "node-gyp-build": "^4.2.2" - } - }, - "node_modules/nice-napi/node_modules/node-addon-api": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", - "dev": true, - "optional": true - }, - "node_modules/node-addon-api": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", - "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", - "dev": true - }, - "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "dev": true, - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/node-gyp": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.3.1.tgz", - "integrity": "sha512-Pp3nFHBThHzVtNY7U6JfPjvT/DTE8+o/4xKsLQtBoU+j2HLsGlhcfzflAoUreaJbNmYnX+LlLi0qjV8kpyO6xQ==", - "dev": true, - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "glob": "^10.3.10", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^13.0.0", - "nopt": "^7.0.0", - "proc-log": "^4.1.0", - "semver": "^7.3.5", - "tar": "^6.2.1", - "which": "^4.0.0" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/node-gyp-build": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", - "dev": true, - "optional": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/node-gyp-build-optional-packages": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", - "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", - "dev": true, - "dependencies": { - "detect-libc": "^2.0.1" - }, - "bin": { - "node-gyp-build-optional-packages": "bin.js", - "node-gyp-build-optional-packages-optional": "optional.js", - "node-gyp-build-optional-packages-test": "build-test.js" - } - }, - "node_modules/node-gyp/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/node-gyp/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "dev": true, - "engines": { - "node": ">=16" - } - }, - "node_modules/node-gyp/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", - "dev": true, - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^16.13.0 || >=18.0.0" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true - }, - "node_modules/nopt": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", - "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", - "dev": true, - "dependencies": { - "abbrev": "^2.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/normalize-package-data": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", - "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", - "dev": true, - "dependencies": { - "hosted-git-info": "^7.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-bundled": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", - "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", - "dev": true, - "dependencies": { - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-install-checks": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", - "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", - "dev": true, - "dependencies": { - "semver": "^7.1.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-normalize-package-bin": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", - "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-package-arg": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", - "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", - "dev": true, - "dependencies": { - "hosted-git-info": "^7.0.0", - "proc-log": "^4.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/npm-packlist": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", - "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", - "dev": true, - "dependencies": { - "ignore-walk": "^6.0.4" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-pick-manifest": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", - "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", - "dev": true, - "dependencies": { - "npm-install-checks": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^11.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/npm-registry-fetch": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-17.1.0.tgz", - "integrity": "sha512-5+bKQRH0J1xG1uZ1zMNvxW0VEyoNWgJpY9UDuluPFLKDfJ9u2JmmjmTJV1srBGQOROfdBMiVvnH2Zvpbm+xkVA==", - "dev": true, - "dependencies": { - "@npmcli/redact": "^2.0.0", - "jsonparse": "^1.3.1", - "make-fetch-happen": "^13.0.0", - "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", - "minizlib": "^2.1.2", - "npm-package-arg": "^11.0.0", - "proc-log": "^4.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/number-allocator": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz", - "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==", - "dependencies": { - "debug": "^4.3.1", - "js-sdsl": "4.3.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "dev": true - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", - "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", - "dev": true, - "dependencies": { - "default-browser": "^5.2.1", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "is-wsl": "^3.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/ordered-binary": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.3.tgz", - "integrity": "sha512-oGFr3T+pYdTGJ+YFEILMpS3es+GiIbs9h/XQrclBXUtd44ey7XwfsMzM31f64I1SQOawDoDr/D823kNCADI8TA==", - "dev": true - }, - "node_modules/ordered-binary": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.2.tgz", - "integrity": "sha512-JTo+4+4Fw7FreyAvlSLjb1BBVaxEQAacmjD3jjuyPZclpbEghTvQZbXBb2qPd2LeIMxiHwXBZUcpmG2Gl/mDEA==", - "dev": true - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-retry": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", - "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", - "dev": true, - "dependencies": { - "@types/retry": "0.12.2", - "is-network-error": "^1.0.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-retry/node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true - }, - "node_modules/pacote": { - "version": "18.0.6", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-18.0.6.tgz", - "integrity": "sha512-+eK3G27SMwsB8kLIuj4h1FUhHtwiEUo21Tw8wNjmvdlpOEr613edv+8FUsTj/4F/VN5ywGE19X18N7CC2EJk6A==", - "dev": true, - "dependencies": { - "@npmcli/git": "^5.0.0", - "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/package-json": "^5.1.0", - "@npmcli/promise-spawn": "^7.0.0", - "@npmcli/run-script": "^8.0.0", - "cacache": "^18.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^7.0.2", - "npm-package-arg": "^11.0.0", - "npm-packlist": "^8.0.0", - "npm-pick-manifest": "^9.0.0", - "npm-registry-fetch": "^17.0.0", - "proc-log": "^4.0.0", - "promise-retry": "^2.0.1", - "sigstore": "^2.2.0", - "ssri": "^10.0.0", - "tar": "^6.1.11" - }, - "bin": { - "pacote": "bin/index.js" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse-json/node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/parse5": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", - "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", - "devOptional": true, - "dependencies": { - "entities": "^4.5.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-html-rewriting-stream": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-7.0.0.tgz", - "integrity": "sha512-mazCyGWkmCRWDI15Zp+UiCqMp/0dgEmkZRvhlsqqKYr4SsVm/TvnSpD9fCvqCA2zoWJcfRym846ejWBBHRiYEg==", - "dev": true, - "dependencies": { - "entities": "^4.3.0", - "parse5": "^7.0.0", - "parse5-sax-parser": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-sax-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", - "integrity": "sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg==", - "dev": true, - "dependencies": { - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "dev": true - }, - "node_modules/path-type": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", - "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true - }, - "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/piscina": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.6.1.tgz", - "integrity": "sha512-z30AwWGtQE+Apr+2WBZensP2lIvwoaMcOPkQlIEmSGMJNUvaYACylPYrQM6wSdUNJlnDVMSpLv7xTMJqlVshOA==", - "dev": true, - "optionalDependencies": { - "nice-napi": "^1.0.2" - } - }, - "node_modules/pkg-dir": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", - "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", - "dev": true, - "dependencies": { - "find-up": "^6.3.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", - "dev": true, - "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", - "dev": true, - "dependencies": { - "p-locate": "^6.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "dev": true, - "dependencies": { - "p-limit": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/pkg-dir/node_modules/yocto-queue": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", - "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", - "dev": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/postcss": { - "version": "8.4.41", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", - "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-loader": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", - "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", - "dev": true, - "dependencies": { - "cosmiconfig": "^9.0.0", - "jiti": "^1.20.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "postcss": "^7.0.0 || ^8.0.1", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/postcss-media-query-parser": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", - "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", - "dev": true - }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", - "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", - "dev": true, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", - "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", - "dev": true, - "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^7.0.0", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-scope": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", - "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "dev": true, - "dependencies": { - "icss-utils": "^5.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-selector-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", - "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", - "dev": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", - "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", - "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-eslint": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-13.0.0.tgz", - "integrity": "sha512-P5K31qWgUOQCtJL/3tpvEe28KfP49qbr6MTVEXC7I2k7ci55bP3YDr+glhyCdhIzxGCVp2f8eobfQ5so52RIIA==", - "dev": true, - "dependencies": { - "@typescript-eslint/parser": "^3.0.0", - "common-tags": "^1.4.0", - "dlv": "^1.1.0", - "eslint": "^7.9.0", - "indent-string": "^4.0.0", - "lodash.merge": "^4.6.0", - "loglevel-colored-level-prefix": "^1.0.0", - "prettier": "^2.0.0", - "pretty-format": "^23.0.1", - "require-relative": "^0.8.7", - "typescript": "^3.9.3", - "vue-eslint-parser": "~7.1.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/prettier-eslint/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/prettier-eslint/node_modules/@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/prettier-eslint/node_modules/@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/prettier-eslint/node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true - }, - "node_modules/prettier-eslint/node_modules/@typescript-eslint/parser": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.10.1.tgz", - "integrity": "sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw==", - "dev": true, - "dependencies": { - "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/experimental-utils": "3.10.1", - "@typescript-eslint/types": "3.10.1", - "@typescript-eslint/typescript-estree": "3.10.1", - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/prettier-eslint/node_modules/@typescript-eslint/types": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", - "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==", - "dev": true, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/prettier-eslint/node_modules/@typescript-eslint/typescript-estree": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", - "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "3.10.1", - "@typescript-eslint/visitor-keys": "3.10.1", - "debug": "^4.1.1", - "glob": "^7.1.6", - "is-glob": "^4.0.1", - "lodash": "^4.17.15", - "semver": "^7.3.2", - "tsutils": "^3.17.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/prettier-eslint/node_modules/@typescript-eslint/visitor-keys": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", - "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/prettier-eslint/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/prettier-eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/prettier-eslint/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/prettier-eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/prettier-eslint/node_modules/eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", - "dev": true, - "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/prettier-eslint/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/prettier-eslint/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/prettier-eslint/node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/prettier-eslint/node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/prettier-eslint/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/prettier-eslint/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prettier-eslint/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/prettier-eslint/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/prettier-eslint/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/prettier-eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/prettier-eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/prettier-eslint/node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-eslint/node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/prettier-eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/prettier-eslint/node_modules/typescript": { - "version": "3.9.10", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", - "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "dependencies": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pretty-format/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/pretty-format/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "dev": true - }, - "node_modules/promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "dev": true, - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-addr/node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", - "dev": true, - "optional": true - }, - "node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "dev": true - }, - "node_modules/qjobs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", - "dev": true, - "engines": { - "node": ">=0.9" - } - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.1.tgz", - "integrity": "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==", - "dev": true, - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/reflect-metadata": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", - "dev": true - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", - "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true - }, - "node_modules/regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regex-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.0.tgz", - "integrity": "sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg==", - "dev": true - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/regexpu-core": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", - "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.0", - "regjsgen": "^0.8.0", - "regjsparser": "^0.12.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "dev": true - }, - "node_modules/regjsparser": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", - "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", - "dev": true, - "dependencies": { - "jsesc": "~3.0.2" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/reinterval": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz", - "integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==" - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-relative": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", - "integrity": "sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==", - "dev": true - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-url-loader": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", - "integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==", - "dev": true, - "dependencies": { - "adjust-sourcemap-loader": "^4.0.0", - "convert-source-map": "^1.7.0", - "loader-utils": "^2.0.0", - "postcss": "^8.2.14", - "source-map": "0.6.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/resolve-url-loader/node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/resolve-url-loader/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "dev": true, - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", - "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", - "dev": true, - "dependencies": { - "@types/estree": "1.0.5" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.22.4", - "@rollup/rollup-android-arm64": "4.22.4", - "@rollup/rollup-darwin-arm64": "4.22.4", - "@rollup/rollup-darwin-x64": "4.22.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", - "@rollup/rollup-linux-arm-musleabihf": "4.22.4", - "@rollup/rollup-linux-arm64-gnu": "4.22.4", - "@rollup/rollup-linux-arm64-musl": "4.22.4", - "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", - "@rollup/rollup-linux-riscv64-gnu": "4.22.4", - "@rollup/rollup-linux-s390x-gnu": "4.22.4", - "@rollup/rollup-linux-x64-gnu": "4.22.4", - "@rollup/rollup-linux-x64-musl": "4.22.4", - "@rollup/rollup-win32-arm64-msvc": "4.22.4", - "@rollup/rollup-win32-ia32-msvc": "4.22.4", - "@rollup/rollup-win32-x64-msvc": "4.22.4", - "fsevents": "~2.3.2" - } - }, - "node_modules/run-applescript": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", - "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/sass": { - "version": "1.77.6", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz", - "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==", - "dev": true, - "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/sass-loader": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.0.tgz", - "integrity": "sha512-n13Z+3rU9A177dk4888czcVFiC8CL9dii4qpXWUg3YIIgZEvi9TCFKjOQcbK0kJM7DJu9VucrZFddvNfYCPwtw==", - "dev": true, - "dependencies": { - "neo-async": "^2.6.2" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", - "sass": "^1.3.0", - "sass-embedded": "*", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "node-sass": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/sass/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/sass/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/sass/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/sass/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/sax": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", - "dev": true, - "optional": true - }, - "node_modules/schema-utils": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", - "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/schema-utils/node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", - "dev": true - }, - "node_modules/selfsigned": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", - "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", - "dev": true, - "dependencies": { - "@types/node-forge": "^1.3.0", - "node-forge": "^1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/send/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/send/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", - "dev": true, - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-index/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true - }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "dev": true, - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-static/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/shell-quote": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", - "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sigstore": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz", - "integrity": "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==", - "dev": true, - "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "@sigstore/sign": "^2.3.2", - "@sigstore/tuf": "^2.3.4", - "@sigstore/verify": "^1.2.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socket.io": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", - "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", - "dev": true, - "dependencies": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "cors": "~2.8.5", - "debug": "~4.3.2", - "engine.io": "~6.6.0", - "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.4" - }, - "engines": { - "node": ">=10.2.0" - } - }, - "node_modules/socket.io-adapter": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", - "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", - "dev": true, - "dependencies": { - "debug": "~4.3.4", - "ws": "~8.17.1" - } - }, - "node_modules/socket.io-adapter/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io-adapter/node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", - "dev": true, - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/sockjs": { - "version": "0.3.24", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", - "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", - "dev": true, - "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" - } - }, - "node_modules/socks": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", - "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", - "dev": true, - "dependencies": { - "ip-address": "^9.0.5", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-loader": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", - "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", - "dev": true, - "dependencies": { - "iconv-lite": "^0.6.3", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.72.1" - } - }, - "node_modules/source-map-loader/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.21", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", - "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", - "dev": true - }, - "node_modules/spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "dev": true, - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dev": true, - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "node_modules/split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", - "dependencies": { - "readable-stream": "^3.0.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true - }, - "node_modules/ssri": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", - "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", - "dev": true, - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/stream-shift": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", - "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==" - }, - "node_modules/streamroller": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", - "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", - "dev": true, - "dependencies": { - "date-format": "^4.0.14", - "debug": "^4.3.4", - "fs-extra": "^8.1.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/symbol-observable": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", - "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/synckit": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", - "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", - "dev": true, - "dependencies": { - "@pkgr/core": "^0.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, - "node_modules/table": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", - "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", - "dev": true, - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/table/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/table/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/table/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "dev": true, - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/terser": { - "version": "5.31.6", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz", - "integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==", - "dev": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.11", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz", - "integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", - "terser": "^5.31.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/thingies": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", - "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", - "dev": true, - "engines": { - "node": ">=10.18" - }, - "peerDependencies": { - "tslib": "^2" - } - }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "dev": true - }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tree-dump": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz", - "integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==", - "dev": true, - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "bin": { - "tree-kill": "cli.js" - } - }, - "node_modules/ts-api-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", - "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", - "dev": true, - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tuf-js": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.1.tgz", - "integrity": "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==", - "dev": true, - "dependencies": { - "@tufjs/models": "2.0.1", - "debug": "^4.3.4", - "make-fetch-happen": "^13.0.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typed-assert": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", - "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", - "dev": true - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" - }, - "node_modules/typescript": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", - "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/ua-parser-js": { - "version": "0.7.40", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.40.tgz", - "integrity": "sha512-us1E3K+3jJppDBa3Tl0L3MOJiGhe1C6P0+nIvQAFYbxlMAx0h81eOwLmU57xgqToduDDPx3y5QsdjPfDu+FgOQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - }, - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - } - ], - "bin": { - "ua-parser-js": "script/cli.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", - "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", - "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/unique-filename": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", - "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", - "dev": true, - "dependencies": { - "unique-slug": "^4.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/unique-slug": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", - "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", - "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/uri-js/node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", - "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", - "dev": true - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/validate-npm-package-name": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", - "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vite": { - "version": "5.4.14", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", - "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", - "dev": true, - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/vite/node_modules/postcss": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", - "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.8", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/vue-eslint-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.1.1.tgz", - "integrity": "sha512-8FdXi0gieEwh1IprIBafpiJWcApwrU+l2FEj8c1HtHFdNXMd0+2jUSjBVmcQYohf/E72irwAXEXLga6TQcB3FA==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "eslint-scope": "^5.0.0", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.2.1", - "esquery": "^1.0.1", - "lodash": "^4.17.15" - }, - "engines": { - "node": ">=8.10" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5.0.0" - } - }, - "node_modules/vue-eslint-parser/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/vue-eslint-parser/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/vue-eslint-parser/node_modules/espree": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", - "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", - "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/vue-eslint-parser/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/watchpack": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", - "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", - "dev": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dev": true, - "dependencies": { - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/weak-lru-cache": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", - "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", - "dev": true - }, - "node_modules/webpack": { - "version": "5.94.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", - "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", - "dev": true, - "dependencies": { - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-dev-middleware": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", - "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", - "dev": true, - "dependencies": { - "colorette": "^2.0.10", - "memfs": "^4.6.0", - "mime-types": "^2.1.31", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - } - } - }, - "node_modules/webpack-dev-server": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.0.4.tgz", - "integrity": "sha512-dljXhUgx3HqKP2d8J/fUMvhxGhzjeNVarDLcbO/EWMSgRizDkxHQDZQaLFL5VJY9tRBj2Gz+rvCEYYvhbqPHNA==", - "dev": true, - "dependencies": { - "@types/bonjour": "^3.5.13", - "@types/connect-history-api-fallback": "^1.5.4", - "@types/express": "^4.17.21", - "@types/serve-index": "^1.9.4", - "@types/serve-static": "^1.15.5", - "@types/sockjs": "^0.3.36", - "@types/ws": "^8.5.10", - "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.2.1", - "chokidar": "^3.6.0", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", - "graceful-fs": "^4.2.6", - "html-entities": "^2.4.0", - "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.1.0", - "launch-editor": "^2.6.1", - "open": "^10.0.3", - "p-retry": "^6.2.0", - "rimraf": "^5.0.5", - "schema-utils": "^4.2.0", - "selfsigned": "^2.4.1", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^7.1.0", - "ws": "^8.16.0" - }, - "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - }, - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-dev-server/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/webpack-dev-server/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/webpack-dev-server/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", - "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", - "dev": true, - "dependencies": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } - } - }, - "node_modules/webpack-dev-server/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/webpack-dev-server/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/rimraf": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", - "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", - "dev": true, - "dependencies": { - "glob": "^10.3.7" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/webpack-merge": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", - "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", - "dev": true, - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack-subresource-integrity": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz", - "integrity": "sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==", - "dev": true, - "dependencies": { - "typed-assert": "^1.0.8" - }, - "engines": { - "node": ">= 12" - }, - "peerDependencies": { - "html-webpack-plugin": ">= 5.0.0-beta.1 < 6", - "webpack": "^5.12.0" - }, - "peerDependenciesMeta": { - "html-webpack-plugin": { - "optional": true - } - } - }, - "node_modules/webpack/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/webpack/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/webpack/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/webpack/node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/webpack/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wildcard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "dev": true - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoctocolors-cjs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", - "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zone.js": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz", - "integrity": "sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ==" - } - } -} diff --git a/resources/report/header_macros.jinja b/resources/report/header_macros.jinja deleted file mode 100644 index 93ae64f4b..000000000 --- a/resources/report/header_macros.jinja +++ /dev/null @@ -1,43 +0,0 @@ -{% macro header(is_first, title, json_data, device, logo, icon_qualification, icon_pilot) %} -{% if is_first %} -
-
- {# Badge #} -

- {% if json_data['device']['test_pack'] == 'Device Qualification' %} - - Device Qualification - {% else %} - - Pilot Assessment - {% endif %} -

-

{{ title }}

-
-

- {{ device['manufacturer'] }} - {{ device['model']}} -

- {% else %} -
-
- {# Badge #} -

- {% if json_data['device']['test_pack'] == 'Device Qualification' %} - - Device Qualification - {% else %} - - Pilot Assessment - {% endif %} -

- {{ title }} -
- - {{ device['manufacturer'] }} - {{ device['model']}} - - {% endif %} - Testrun -
-{% endmacro %} \ No newline at end of file diff --git a/resources/report/test_report_template.html b/resources/report/test_report_template.html deleted file mode 100644 index fbd8d1c68..000000000 --- a/resources/report/test_report_template.html +++ /dev/null @@ -1,241 +0,0 @@ -{% import 'header_macros.jinja' as header_macros %} - - - - - - - Testrun Report - - - - - {% set page_index = namespace(value=0) %} - {# Test Results #} - {% for page in range(pages_num) %} - {% set page_index.value = page_index.value+1 %} -
- {{ header_macros.header(loop.first, "Testrun report", json_data, device, logo, icon_qualification, icon_pilot)}} - {% if loop.first %} -
-
-
-

Manufacturer

-
{{ device['manufacturer']}}
-
-

Model

-
{{ device['model'] }}
-
-

Firmware

-
{{ device['firmware']}}
-
-

MAC Address

-
{{ device['mac_addr'] }}
-
-
-
-
-

Device Configuration

-
- {% for module, enabled in modules.items() %} -
- {% if enabled %} - - {% else %} - - {% endif %} - {{ module }} -
- {% endfor %} -
- {% if test_status == 'Compliant' %} -
- {% else %} -
- {% endif %} -
Test Status
-
Complete
-
Test Result
-
{{ test_status }}
-
Started
-
{{ json_data['started']}}
-
Duration
-
- {% if duration.seconds//3600 > 0 %}{{ duration.seconds//3600 }}h {% endif %} - {% if duration.seconds//60 > 0 %}{{ duration.seconds//60 }}m {% endif %} - {{ duration.seconds%60 }}s -
-
-
- {% endif %} - {% if loop.first %} - {% set results_from = 0 %} - {% set results_to = [tests_first_page, test_results|length]|min %} - {% else %} - {% set results_from = tests_first_page + (loop.index0 - 1) * tests_per_page %} - {% set results_to = [results_from + tests_per_page, test_results|length]|min %} - {% endif %} -
-

Results List ({{ successful_tests }}/{{ total_tests }})

-
-
Name
-
Description
-
Result
-
- {% for i in range(results_from, results_to) %} -
-
{{ test_results[i]['name'] }}
-
{{ test_results[i]['description'] }}
- {% if test_results[i]['result'] == 'Non-Compliant' %} -
- {% elif test_results[i]['result'] == 'Compliant' %} -
- {% elif test_results[i]['result'] == 'Error' %} -
- {% elif test_results[i]['result'] == 'Feature Not Detected' %} -
- {% elif test_results[i]['result'] == 'Informational' %} -
- {% else %} -
- {% endif %} - {{ test_results[i]['result'] }}
-
- {% endfor %} -
- -
-
- {% endfor %} - {# Steps to resolve Device qualification #} - {% if steps_to_resolve|length > 0 and json_data['device']['test_pack'] == 'Device Qualification' %} - {% set page_index.value = page_index.value+1 %} -
- {{ header_macros.header(False, "Testrun report", json_data, device, logo, icon_qualification, icon_pilot)}} -

Non-compliant tests and suggested steps to resolve

- {% for step in steps_to_resolve %} -
-
- {{ loop.index }}. -
- Name
{{ step['name'] }} -
-
- Description
{{ step["description"] }} -
-
-
- Steps to resolve - {% for recommedtation in step['recommendations'] %} -
{{ loop.index }}. {{ recommedtation }} - {% endfor %} -
-
- {% endfor %} - -
- {% endif %} - {# Modules reports #} - {% for module in module_reports %} - {% set page_index.value = page_index.value+1 %} -
- {{ header_macros.header(False, "Testrun report", json_data, device, logo, icon_qualification, icon_pilot)}} -
- {{ module }} -
- -
-
- {% endfor %} - {# Device profile #} - {% set page_index.value = page_index.value+1 %} -
- {{ header_macros.header(False, "Testrun report", json_data, device, logo, icon_qualification, icon_pilot)}} -

Device profile

-
-
-
Question
-
Answer
-
- {% for question in json_data['device']['device_profile'] %} -
-
{{loop.index}}.
-
{{ question['question'] }}
-
- {% if question['answer'] is string %} - {{ question['answer'] }} - {% elif question['answer'] is sequence %} -
    - {% for answer in question['answer'] %} -
  • {{ answer }}
  • - {% endfor %} -
- {% endif %} -
-
- {% endfor %} -
- -
-
- {# Pilot steps to resolve#} - {% if json_data['device']['test_pack'] == 'Pilot Assessment' and optional_steps_to_resolve|length > 0 %} - {% set page_index.value = page_index.value + 1 %} -
- {{ header_macros.header(False, "Testrun report", json_data, device, logo, icon_qualification, icon_pilot)}} -

Recommendations for Device Qualification

-
-

Attention

-

- The following recommendations are required solely for full device qualification. - They are optional for the pilot assessment. - But you may find it valuable to understand what will be required in the future - and our recommendations for your device. -

-
- {% for step in optional_steps_to_resolve %} -
-
- - {{ loop.index }}. - -
- Name
- {{ step['name'] }} -
-
- Description
- {{ step["description"] }} -
-
-
- Steps to resolve - {% for recommedtation in step['optional_recommendations'] %} -
- - {{ loop.index }}. {{ recommedtation }} - - {% endfor %} -
-
- {% endfor %} - -
- {% endif %} - - From e84504a1daf6ad5263f1cb8a484b70ec7f9427f1 Mon Sep 17 00:00:00 2001 From: Jacob Boddey Date: Fri, 21 Feb 2025 16:29:27 +0000 Subject: [PATCH 12/12] Fix merge issues --- modules/ui/.gitignore | 2 - modules/ui/package-lock.json | 14641 ++++++++++++++++++++++ resources/report/pilot-icon.png | Bin 536 -> 0 bytes resources/report/qualification-icon.png | Bin 428 -> 0 bytes 4 files changed, 14641 insertions(+), 2 deletions(-) create mode 100644 modules/ui/package-lock.json delete mode 100644 resources/report/pilot-icon.png delete mode 100644 resources/report/qualification-icon.png diff --git a/modules/ui/.gitignore b/modules/ui/.gitignore index 1b50068e8..d53004b88 100644 --- a/modules/ui/.gitignore +++ b/modules/ui/.gitignore @@ -41,5 +41,3 @@ testem.log # System files .DS_Store Thumbs.db - -package-lock.json diff --git a/modules/ui/package-lock.json b/modules/ui/package-lock.json new file mode 100644 index 000000000..98a6a8247 --- /dev/null +++ b/modules/ui/package-lock.json @@ -0,0 +1,14641 @@ +{ + "name": "test-run-ui", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "test-run-ui", + "version": "0.0.0", + "dependencies": { + "@angular/animations": "^18.2.4", + "@angular/cdk": "^18.2.0", + "@angular/common": "^18.2.4", + "@angular/compiler": "^18.2.4", + "@angular/core": "^18.2.4", + "@angular/forms": "^18.2.4", + "@angular/material": "^18.2.0", + "@angular/platform-browser": "^18.2.4", + "@angular/platform-browser-dynamic": "^18.2.4", + "@angular/router": "^18.2.4", + "@ngrx/component-store": "^18.0.2", + "@ngrx/effects": "^18.0.2", + "@ngrx/store": "^18.0.2", + "ngx-mask": "^16.4.2", + "ngx-mqtt": "^17.0.0", + "rxjs": "~7.8.0", + "tslib": "^2.6.2", + "zone.js": "^0.14.10" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^18.1.4", + "@angular-eslint/builder": "18.3.0", + "@angular-eslint/eslint-plugin": "^18.3.0", + "@angular-eslint/eslint-plugin-template": "^18.3.0", + "@angular-eslint/schematics": "^18.3.0", + "@angular-eslint/template-parser": "18.3.0", + "@angular/cli": "~18.2.4", + "@angular/compiler-cli": "^18.2.4", + "@types/jasmine": "~4.3.6", + "@typescript-eslint/eslint-plugin": "^8.2.0", + "@typescript-eslint/parser": "^8.2.0", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", + "jasmine-core": "~4.6.0", + "karma": "~6.4.0", + "karma-chrome-launcher": "~3.2.0", + "karma-coverage": "~2.2.0", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.1.0", + "prettier": "^3.2.5", + "prettier-eslint": "^13.0.0", + "typescript": "~5.5.4" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@angular-devkit/architect": { + "version": "0.1802.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.14.tgz", + "integrity": "sha512-eplaGCXSlPwf1f4XwyzsYTd8/lJ0/Adm6XsODsBxvkZlIpLcps80/h2lH5MVJpoDREzIFu1BweDpYCoNK5yYZg==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "18.2.14", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/build-angular": { + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.14.tgz", + "integrity": "sha512-ycie4OhvNv8eNVqvq46pCIf6kB50xbMOdnAVqmlj+BaQjWbGjUQPjAmp4VGqeDZZ/lW82xkfTmJZxc6pYp7YdQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.1802.14", + "@angular-devkit/build-webpack": "0.1802.14", + "@angular-devkit/core": "18.2.14", + "@angular/build": "18.2.14", + "@babel/core": "7.25.2", + "@babel/generator": "7.25.0", + "@babel/helper-annotate-as-pure": "7.24.7", + "@babel/helper-split-export-declaration": "7.24.7", + "@babel/plugin-transform-async-generator-functions": "7.25.0", + "@babel/plugin-transform-async-to-generator": "7.24.7", + "@babel/plugin-transform-runtime": "7.24.7", + "@babel/preset-env": "7.25.3", + "@babel/runtime": "7.25.0", + "@discoveryjs/json-ext": "0.6.1", + "@ngtools/webpack": "18.2.14", + "@vitejs/plugin-basic-ssl": "1.1.0", + "ansi-colors": "4.1.3", + "autoprefixer": "10.4.20", + "babel-loader": "9.1.3", + "browserslist": "^4.21.5", + "copy-webpack-plugin": "12.0.2", + "critters": "0.0.24", + "css-loader": "7.1.2", + "esbuild-wasm": "0.23.0", + "fast-glob": "3.3.2", + "http-proxy-middleware": "3.0.3", + "https-proxy-agent": "7.0.5", + "istanbul-lib-instrument": "6.0.3", + "jsonc-parser": "3.3.1", + "karma-source-map-support": "1.4.0", + "less": "4.2.0", + "less-loader": "12.2.0", + "license-webpack-plugin": "4.0.2", + "loader-utils": "3.3.1", + "magic-string": "0.30.11", + "mini-css-extract-plugin": "2.9.0", + "mrmime": "2.0.0", + "open": "10.1.0", + "ora": "5.4.1", + "parse5-html-rewriting-stream": "7.0.0", + "picomatch": "4.0.2", + "piscina": "4.6.1", + "postcss": "8.4.41", + "postcss-loader": "8.1.1", + "resolve-url-loader": "5.0.0", + "rxjs": "7.8.1", + "sass": "1.77.6", + "sass-loader": "16.0.0", + "semver": "7.6.3", + "source-map-loader": "5.0.0", + "source-map-support": "0.5.21", + "terser": "5.31.6", + "tree-kill": "1.2.2", + "tslib": "2.6.3", + "watchpack": "2.4.1", + "webpack": "5.94.0", + "webpack-dev-middleware": "7.4.2", + "webpack-dev-server": "5.0.4", + "webpack-merge": "6.0.1", + "webpack-subresource-integrity": "5.1.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "optionalDependencies": { + "esbuild": "0.23.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^18.0.0", + "@angular/localize": "^18.0.0", + "@angular/platform-server": "^18.0.0", + "@angular/service-worker": "^18.0.0", + "@web/test-runner": "^0.18.0", + "browser-sync": "^3.0.2", + "jest": "^29.5.0", + "jest-environment-jsdom": "^29.5.0", + "karma": "^6.3.0", + "ng-packagr": "^18.0.0", + "protractor": "^7.0.0", + "tailwindcss": "^2.0.0 || ^3.0.0", + "typescript": ">=5.4 <5.6" + }, + "peerDependenciesMeta": { + "@angular/localize": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "@web/test-runner": { + "optional": true + }, + "browser-sync": { + "optional": true + }, + "jest": { + "optional": true + }, + "jest-environment-jsdom": { + "optional": true + }, + "karma": { + "optional": true + }, + "ng-packagr": { + "optional": true + }, + "protractor": { + "optional": true + }, + "tailwindcss": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true + }, + "node_modules/@angular-devkit/build-webpack": { + "version": "0.1802.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.14.tgz", + "integrity": "sha512-cccne0SG4BaQHsKRRZCi/wMLJ7yFXrwvE8w+Kug3HdpJJoyH3FeG386EQuca/azslQlK+c5g4ywSZdXeNkGazA==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1802.14", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "webpack": "^5.30.0", + "webpack-dev-server": "^5.0.2" + } + }, + "node_modules/@angular-devkit/core": { + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.14.tgz", + "integrity": "sha512-UGIGOjXuOyCW+5S4tINu7e6LOu738CmTw3h7Ui1I8OzdTIYJcYJrei8sgrwDwOYADRal+p0MeMlnykH3TM5XBA==", + "dev": true, + "dependencies": { + "ajv": "8.17.1", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/schematics": { + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.14.tgz", + "integrity": "sha512-mukjZIHHB7gWratq8fZwUq5WZ+1bF4feG/idXr1wgQ+/FqWjs2PP7HDesHVcPymmRulpTyCpB7TNB1O1fgnCpA==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "18.2.14", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.11", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-eslint/builder": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-18.3.0.tgz", + "integrity": "sha512-httEQyqyBw3+0CRtAa7muFxHrauRfkEfk/jmrh5fn2Eiu+I53hAqFPgrwVi1V6AP/kj2zbAiWhd5xM3pMJdoRQ==", + "dev": true, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/bundled-angular-compiler": { + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.4.3.tgz", + "integrity": "sha512-zdrA8mR98X+U4YgHzUKmivRU+PxzwOL/j8G7eTOvBuq8GPzsP+hvak+tyxlgeGm9HsvpFj9ERHLtJ0xDUPs8fg==", + "dev": true + }, + "node_modules/@angular-eslint/eslint-plugin": { + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-18.4.3.tgz", + "integrity": "sha512-AyJbupiwTBR81P6T59v+aULEnPpZBCBxL2S5QFWfAhNCwWhcof4GihvdK2Z87yhvzDGeAzUFSWl/beJfeFa+PA==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "18.4.3", + "@angular-eslint/utils": "18.4.3" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template": { + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-18.4.3.tgz", + "integrity": "sha512-ijGlX2N01ayMXTpeQivOA31AszO8OEbu9ZQUCxnu9AyMMhxyi2q50bujRChAvN9YXQfdQtbxuajxV6+aiWb5BQ==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "18.4.3", + "@angular-eslint/utils": "18.4.3", + "aria-query": "5.3.2", + "axobject-query": "4.1.0" + }, + "peerDependencies": { + "@typescript-eslint/types": "^7.11.0 || ^8.0.0", + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/schematics": { + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-18.4.3.tgz", + "integrity": "sha512-D5maKn5e6n58+8n7jLFLD4g+RGPOPeDSsvPc1sqial5tEKLxAJQJS9WZ28oef3bhkob6C60D+1H0mMmEEVvyVA==", + "dev": true, + "dependencies": { + "@angular-devkit/core": ">= 18.0.0 < 19.0.0", + "@angular-devkit/schematics": ">= 18.0.0 < 19.0.0", + "@angular-eslint/eslint-plugin": "18.4.3", + "@angular-eslint/eslint-plugin-template": "18.4.3", + "ignore": "6.0.2", + "semver": "7.6.3", + "strip-json-comments": "3.1.1" + } + }, + "node_modules/@angular-eslint/template-parser": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-18.3.0.tgz", + "integrity": "sha512-1mUquqcnugI4qsoxcYZKZ6WMi6RPelDcJZg2YqGyuaIuhWmi3ZqJZLErSSpjP60+TbYZu7wM8Kchqa1bwJtEaQ==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "18.3.0", + "eslint-scope": "^8.0.2" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/template-parser/node_modules/@angular-eslint/bundled-angular-compiler": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.3.0.tgz", + "integrity": "sha512-v/59FxUKnMzymVce99gV43huxoqXWMb85aKvzlNvLN+ScDu6ZE4YMiTQNpfapVL2lkxhs0uwB3jH17EYd5TcsA==", + "dev": true + }, + "node_modules/@angular-eslint/utils": { + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-18.4.3.tgz", + "integrity": "sha512-w0bJ9+ELAEiPBSTPPm9bvDngfu1d8JbzUhvs2vU+z7sIz/HMwUZT5S4naypj2kNN0gZYGYrW0lt+HIbW87zTAQ==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "18.4.3" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular/animations": { + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.2.13.tgz", + "integrity": "sha512-rG5J5Ek5Hg+Tz2NjkNOaG6PupiNK/lPfophXpsR1t/nWujqnMWX2krahD/i6kgD+jNWNKCJCYSOVvCx/BHOtKA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/core": "18.2.13" + } + }, + "node_modules/@angular/build": { + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.14.tgz", + "integrity": "sha512-9g24Oe/ZLULacW3hEpRCjSZIJPJTzN5BeFbA27epSV5NsrQOoeUGsEpRs90Zmt6eReO0fW1BGshWRoZtpSedcw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.1802.14", + "@babel/core": "7.25.2", + "@babel/helper-annotate-as-pure": "7.24.7", + "@babel/helper-split-export-declaration": "7.24.7", + "@babel/plugin-syntax-import-attributes": "7.24.7", + "@inquirer/confirm": "3.1.22", + "@vitejs/plugin-basic-ssl": "1.1.0", + "browserslist": "^4.23.0", + "critters": "0.0.24", + "esbuild": "0.23.0", + "fast-glob": "3.3.2", + "https-proxy-agent": "7.0.5", + "listr2": "8.2.4", + "lmdb": "3.0.13", + "magic-string": "0.30.11", + "mrmime": "2.0.0", + "parse5-html-rewriting-stream": "7.0.0", + "picomatch": "4.0.2", + "piscina": "4.6.1", + "rollup": "4.22.4", + "sass": "1.77.6", + "semver": "7.6.3", + "vite": "5.4.14", + "watchpack": "2.4.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^18.0.0", + "@angular/localize": "^18.0.0", + "@angular/platform-server": "^18.0.0", + "@angular/service-worker": "^18.0.0", + "less": "^4.2.0", + "postcss": "^8.4.0", + "tailwindcss": "^2.0.0 || ^3.0.0", + "typescript": ">=5.4 <5.6" + }, + "peerDependenciesMeta": { + "@angular/localize": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "less": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tailwindcss": { + "optional": true + } + } + }, + "node_modules/@angular/cdk": { + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.14.tgz", + "integrity": "sha512-vDyOh1lwjfVk9OqoroZAP8pf3xxKUvyl+TVR8nJxL4c5fOfUFkD7l94HaanqKSRwJcI2xiztuu92IVoHn8T33Q==", + "dependencies": { + "tslib": "^2.3.0" + }, + "optionalDependencies": { + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@angular/common": "^18.0.0 || ^19.0.0", + "@angular/core": "^18.0.0 || ^19.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/cli": { + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.14.tgz", + "integrity": "sha512-kWgRRQtJPkr8iwN7DMbTi3sXOnv7H5QhbU/GgD3nNX3D8YCSPmnby4PAE/P3wn7FsIK9JsSchsCt7MZ37Urh9A==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1802.14", + "@angular-devkit/core": "18.2.14", + "@angular-devkit/schematics": "18.2.14", + "@inquirer/prompts": "5.3.8", + "@listr2/prompt-adapter-inquirer": "2.0.15", + "@schematics/angular": "18.2.14", + "@yarnpkg/lockfile": "1.1.0", + "ini": "4.1.3", + "jsonc-parser": "3.3.1", + "listr2": "8.2.4", + "npm-package-arg": "11.0.3", + "npm-pick-manifest": "9.1.0", + "pacote": "18.0.6", + "resolve": "1.22.8", + "semver": "7.6.3", + "symbol-observable": "4.0.0", + "yargs": "17.7.2" + }, + "bin": { + "ng": "bin/ng.js" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/common": { + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-18.2.13.tgz", + "integrity": "sha512-4ZqrNp1PoZo7VNvW+sbSc2CB2axP1sCH2wXl8B0wdjsj8JY1hF1OhuugwhpAHtGxqewed2kCXayE+ZJqSTV4jw==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/core": "18.2.13", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/compiler": { + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-18.2.13.tgz", + "integrity": "sha512-TzWcrkopyjFF+WeDr2cRe8CcHjU72KfYV3Sm2TkBkcXrkYX5sDjGWrBGrG3hRB4e4okqchrOCvm1MiTdy2vKMA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/core": "18.2.13" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + } + } + }, + "node_modules/@angular/compiler-cli": { + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-18.2.13.tgz", + "integrity": "sha512-DBSh4AQwkiJDSiVvJATRmjxf6wyUs9pwQLgaFdSlfuTRO+sdb0J2z1r3BYm8t0IqdoyXzdZq2YCH43EmyvD71g==", + "dev": true, + "dependencies": { + "@babel/core": "7.25.2", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^4.0.0", + "convert-source-map": "^1.5.1", + "reflect-metadata": "^0.2.0", + "semver": "^7.0.0", + "tslib": "^2.3.0", + "yargs": "^17.2.1" + }, + "bin": { + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js", + "ngcc": "bundles/ngcc/index.js" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/compiler": "18.2.13", + "typescript": ">=5.4 <5.6" + } + }, + "node_modules/@angular/compiler-cli/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@angular/compiler-cli/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@angular/core": { + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.13.tgz", + "integrity": "sha512-8mbWHMgO95OuFV1Ejy4oKmbe9NOJ3WazQf/f7wks8Bck7pcihd0IKhlPBNjFllbF5o+04EYSwFhEtvEgjMDClA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.14.10" + } + }, + "node_modules/@angular/forms": { + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.2.13.tgz", + "integrity": "sha512-A67D867fu3DSBhdLWWZl/F5pr7v2+dRM2u3U7ZJ0ewh4a+sv+0yqWdJW+a8xIoiHxS+btGEJL2qAKJiH+MCFfg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/common": "18.2.13", + "@angular/core": "18.2.13", + "@angular/platform-browser": "18.2.13", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/material": { + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-18.2.14.tgz", + "integrity": "sha512-28pxzJP49Mymt664WnCtPkKeg7kXUsQKTKGf/Kl95rNTEdTJLbnlcc8wV0rT0yQNR7kXgpfBnG7h0ETLv/iu5Q==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/animations": "^18.0.0 || ^19.0.0", + "@angular/cdk": "18.2.14", + "@angular/common": "^18.0.0 || ^19.0.0", + "@angular/core": "^18.0.0 || ^19.0.0", + "@angular/forms": "^18.0.0 || ^19.0.0", + "@angular/platform-browser": "^18.0.0 || ^19.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/platform-browser": { + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.13.tgz", + "integrity": "sha512-tu7ZzY6qD3ATdWFzcTcsAKe7M6cJeWbT/4/bF9unyGO3XBPcNYDKoiz10+7ap2PUd0fmPwvuvTvSNJiFEBnB8Q==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/animations": "18.2.13", + "@angular/common": "18.2.13", + "@angular/core": "18.2.13" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } + } + }, + "node_modules/@angular/platform-browser-dynamic": { + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-18.2.13.tgz", + "integrity": "sha512-kbQCf9+8EpuJC7buBxhSiwBtXvjAwAKh6MznD6zd2pyCYqfY6gfRCZQRtK59IfgVtKmEONWI9grEyNIRoTmqJg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/common": "18.2.13", + "@angular/compiler": "18.2.13", + "@angular/core": "18.2.13", + "@angular/platform-browser": "18.2.13" + } + }, + "node_modules/@angular/router": { + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-18.2.13.tgz", + "integrity": "sha512-VKmfgi/r/CkyBq9nChQ/ptmfu0JT/8ONnLVJ5H+SkFLRYJcIRyHLKjRihMCyVm6xM5yktOdCaW73NTQrFz7+bg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/common": "18.2.13", + "@angular/core": "18.2.13", + "@angular/platform-browser": "18.2.13", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.5.tgz", + "integrity": "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", + "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz", + "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.25.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz", + "integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "regexpu-core": "^6.2.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", + "integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", + "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", + "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", + "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-wrap-function": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", + "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", + "dev": true, + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", + "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", + "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", + "dev": true, + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz", + "integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==", + "dev": true, + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.9.tgz", + "integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz", + "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.26.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", + "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", + "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", + "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", + "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", + "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", + "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.0.tgz", + "integrity": "sha512-uaIi2FdqzjpAMvVqvB51S42oC2JEVgh0LDsGfZVDysWE8LrJtQC2jvKmOqEYThKyB7bDEb7BP1GYWDm7tABA0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-remap-async-to-generator": "^7.25.0", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/traverse": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", + "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz", + "integrity": "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", + "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", + "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", + "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", + "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/traverse": "^7.25.9", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", + "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/template": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", + "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", + "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", + "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", + "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", + "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", + "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", + "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", + "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", + "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", + "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", + "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", + "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", + "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", + "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", + "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", + "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", + "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.26.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz", + "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", + "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", + "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", + "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", + "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", + "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", + "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", + "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", + "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", + "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", + "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", + "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", + "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", + "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", + "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", + "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.26.7.tgz", + "integrity": "sha512-jfoTXXZTgGg36BmhqT3cAYK5qkmqvJpvNrPhaK/52Vgjhw4Rq29s9UqpWWV0D6yuRmgiFH/BUVlkl96zJWqnaw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", + "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", + "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", + "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", + "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.3.tgz", + "integrity": "sha512-QsYW7UeAaXvLPX9tdVliMJE7MD7M6MLYVTovRTIwhoYQVFHR1rM4wO8wqAezYi3/BpSD+NzVCZ69R6smWiIi8g==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.25.2", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.3", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.24.7", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.0", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoped-functions": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-class-static-block": "^7.24.7", + "@babel/plugin-transform-classes": "^7.25.0", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-dotall-regex": "^7.24.7", + "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0", + "@babel/plugin-transform-dynamic-import": "^7.24.7", + "@babel/plugin-transform-exponentiation-operator": "^7.24.7", + "@babel/plugin-transform-export-namespace-from": "^7.24.7", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.25.1", + "@babel/plugin-transform-json-strings": "^7.24.7", + "@babel/plugin-transform-literals": "^7.25.2", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-member-expression-literals": "^7.24.7", + "@babel/plugin-transform-modules-amd": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-modules-systemjs": "^7.25.0", + "@babel/plugin-transform-modules-umd": "^7.24.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-new-target": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-object-super": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-property-literals": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-reserved-words": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-template-literals": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.8", + "@babel/plugin-transform-unicode-escapes": "^7.24.7", + "@babel/plugin-transform-unicode-property-regex": "^7.24.7", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.37.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", + "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz", + "integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.5", + "@babel/parser": "^7.26.7", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.7", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/generator": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz", + "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.26.5", + "@babel/types": "^7.26.5", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/types": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz", + "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.1.tgz", + "integrity": "sha512-boghen8F0Q8D+0/Q1/1r6DUEieUJ8w2a1gIknExMSHBsJFOr2+0KUfHiVYBvucPwl3+RU5PFBK833FjFCh3BhA==", + "dev": true, + "engines": { + "node": ">=14.17.0" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz", + "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "node_modules/@inquirer/checkbox": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-2.5.0.tgz", + "integrity": "sha512-sMgdETOfi2dUHT8r7TT1BTKOwNvdDGFDXYWtQ2J69SvlYNntk9I/gJe7r5yvMwwsuKnYbuRs3pNhx4tgNck5aA==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.3", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/confirm": { + "version": "3.1.22", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.1.22.tgz", + "integrity": "sha512-gsAKIOWBm2Q87CDfs9fEo7wJT3fwWIJfnDGMn9Qy74gBnNFOACDNfhUzovubbJjWnKLGBln7/NcSmZwj5DuEXg==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.2.1.tgz", + "integrity": "sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==", + "dev": true, + "dependencies": { + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "@types/mute-stream": "^0.0.4", + "@types/node": "^22.5.5", + "@types/wrap-ansi": "^3.0.0", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^1.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core/node_modules/@inquirer/type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-2.0.0.tgz", + "integrity": "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==", + "dev": true, + "dependencies": { + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-2.2.0.tgz", + "integrity": "sha512-9KHOpJ+dIL5SZli8lJ6xdaYLPPzB8xB9GZItg39MBybzhxA16vxmszmQFrRwbOA918WA2rvu8xhDEg/p6LXKbw==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "external-editor": "^3.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/expand": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-2.3.0.tgz", + "integrity": "sha512-qnJsUcOGCSG1e5DTOErmv2BPQqrtT6uzqn1vI/aYGiPKq+FgslGZmtdnXbhuI7IlT7OByDoEEqdnhUnVR2hhLw==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.10.tgz", + "integrity": "sha512-Ey6176gZmeqZuY/W/nZiUyvmb1/qInjcpiZjXWi6nON+nxJpD1bxtSoBxNliGISae32n6OwbY+TSXPZ1CfS4bw==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-2.3.0.tgz", + "integrity": "sha512-XfnpCStx2xgh1LIRqPXrTNEEByqQWoxsWYzNRSEUxJ5c6EQlhMogJ3vHKu8aXuTacebtaZzMAHwEL0kAflKOBw==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/number": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-1.1.0.tgz", + "integrity": "sha512-ilUnia/GZUtfSZy3YEErXLJ2Sljo/mf9fiKc08n18DdwdmDbOzRcTv65H1jjDvlsAuvdFXf4Sa/aL7iw/NanVA==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/password": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-2.2.0.tgz", + "integrity": "sha512-5otqIpgsPYIshqhgtEwSspBQE40etouR8VIxzpJkv9i0dVHIpyhiivbkH9/dGiMLdyamT54YRdGJLfl8TFnLHg==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "ansi-escapes": "^4.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/prompts": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-5.3.8.tgz", + "integrity": "sha512-b2BudQY/Si4Y2a0PdZZL6BeJtl8llgeZa7U2j47aaJSCeAl1e4UI7y8a9bSkO3o/ZbZrgT5muy/34JbsjfIWxA==", + "dev": true, + "dependencies": { + "@inquirer/checkbox": "^2.4.7", + "@inquirer/confirm": "^3.1.22", + "@inquirer/editor": "^2.1.22", + "@inquirer/expand": "^2.1.22", + "@inquirer/input": "^2.2.9", + "@inquirer/number": "^1.0.10", + "@inquirer/password": "^2.1.22", + "@inquirer/rawlist": "^2.2.4", + "@inquirer/search": "^1.0.7", + "@inquirer/select": "^2.4.7" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/rawlist": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-2.3.0.tgz", + "integrity": "sha512-zzfNuINhFF7OLAtGHfhwOW2TlYJyli7lOUoJUXw/uyklcwalV6WRXBXtFIicN8rTRK1XTiPWB4UY+YuW8dsnLQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/search": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-1.1.0.tgz", + "integrity": "sha512-h+/5LSj51dx7hp5xOn4QFnUaKeARwUCLs6mIhtkJ0JYPBLmEYjdHSYh7I6GrLg9LwpJ3xeX0FZgAG1q0QdCpVQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.3", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/select": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-2.5.0.tgz", + "integrity": "sha512-YmDobTItPP3WcEI86GvPo+T2sRHkxxOq/kXmsBjHS5BVXUgvgZ5AfJjkvQvZr03T81NnI3KrrRuMzeuYUQRFOA==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.3", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.5.5.tgz", + "integrity": "sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==", + "dev": true, + "dependencies": { + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.1.tgz", + "integrity": "sha512-osjeBqMJ2lb/j/M8NCPjs1ylqWIcTRTycIhVB5pt6LgzgeRSb0YRZ7j9RfA8wIUrsr/medIuhVyonXRZWLyfdw==", + "dev": true, + "dependencies": { + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.5.0.tgz", + "integrity": "sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true + }, + "node_modules/@listr2/prompt-adapter-inquirer": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-2.0.15.tgz", + "integrity": "sha512-MZrGem/Ujjd4cPTLYDfCZK2iKKeiO/8OX13S6jqxldLs0Prf2aGqVlJ77nMBqMv7fzqgXEgjrNHLXcKR8l9lOg==", + "dev": true, + "dependencies": { + "@inquirer/type": "^1.5.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@inquirer/prompts": ">= 3 < 6" + } + }, + "node_modules/@lmdb/lmdb-linux-x64": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.0.13.tgz", + "integrity": "sha512-vOtxu0xC0SLdQ2WRXg8Qgd8T32ak4SPqk5zjItRszrJk2BdeXqfGxBJbP7o4aOvSPSmSSv46Lr1EP4HXU8v7Kg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ngrx/component-store": { + "version": "18.1.1", + "resolved": "https://registry.npmjs.org/@ngrx/component-store/-/component-store-18.1.1.tgz", + "integrity": "sha512-+FDd44D+unx/eE/7qCyK1IDsTu8503JOnj3lEhL9f9TDmE4arDNqeNx/8Sh3V3HDkH7mVC9iV0c538ACGlvWIA==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/core": "^18.0.0", + "rxjs": "^6.5.3 || ^7.5.0" + } + }, + "node_modules/@ngrx/effects": { + "version": "18.1.1", + "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-18.1.1.tgz", + "integrity": "sha512-XXob8kYEvYMaZwgHtrrTW0XZargbu5PloEpNHLnzB8jPk0yWEw6keryxaF09Ylws1779MWvMmF/YP2rPl04nHQ==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/core": "^18.0.0", + "@ngrx/store": "18.1.1", + "rxjs": "^6.5.3 || ^7.5.0" + } + }, + "node_modules/@ngrx/store": { + "version": "18.1.1", + "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-18.1.1.tgz", + "integrity": "sha512-K0v1akJ2sEnIeb1AUA064+ksgRgbMgVG9HbSsLBxENbFjK2ZvKRxo1bpOw6WHW9+hyDTlhZGl7+gUtjmo3497g==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/core": "^18.0.0", + "rxjs": "^6.5.3 || ^7.5.0" + } + }, + "node_modules/@ngtools/webpack": { + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.14.tgz", + "integrity": "sha512-rT+Y4WR8QTVsijtb+YRqHcPTpd1ZiwRbklQXRTxU0YGFHpxpi+bhjmY8FjpPoAtdPO1Lg3l3KIZPZa0thG0FNg==", + "dev": true, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^18.0.0", + "typescript": ">=5.4 <5.6", + "webpack": "^5.54.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/agent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", + "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/@npmcli/fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", + "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", + "integrity": "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==", + "dev": true, + "dependencies": { + "@npmcli/promise-spawn": "^7.0.0", + "ini": "^4.1.3", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^4.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/@npmcli/git/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", + "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", + "dev": true, + "dependencies": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", + "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.2.1.tgz", + "integrity": "sha512-f7zYC6kQautXHvNbLEWgD/uGu1+xCn9izgqBfgItWSx22U0ZDekxN08A1vM8cTxj/cRVe0Q94Ode+tdoYmIOOQ==", + "dev": true, + "dependencies": { + "@npmcli/git": "^5.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^7.0.0", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "proc-log": "^4.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", + "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", + "dev": true, + "dependencies": { + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-2.0.1.tgz", + "integrity": "sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-8.1.0.tgz", + "integrity": "sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==", + "dev": true, + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^10.0.0", + "proc-log": "^4.0.0", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/run-script/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", + "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", + "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@schematics/angular": { + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.14.tgz", + "integrity": "sha512-CHh6ew2Az71UlvVcnYeuMEwjwkZqR7y/9ebLzFRvczC71ZL8qPVBpBTVGbCpGBd54VEbCZVWRxBQoZZ5LP/aBw==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "18.2.14", + "@angular-devkit/schematics": "18.2.14", + "jsonc-parser": "3.3.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@sigstore/bundle": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", + "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", + "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.1.0.tgz", + "integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.3.tgz", + "integrity": "sha512-RpacQhBlwpBWd7KEJsRKcBQalbV28fvkxwTOJIqhIuDysMMaJW47V4OqW30iJB9uRpqOSxxEAQFdr8tTattReQ==", + "dev": true, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.2.tgz", + "integrity": "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "make-fetch-happen": "^13.0.1", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/tuf": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.4.tgz", + "integrity": "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==", + "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2", + "tuf-js": "^2.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/verify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.1.tgz", + "integrity": "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.1.0", + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "dev": true + }, + "node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.1.tgz", + "integrity": "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==", + "dev": true, + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", + "dev": true + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", + "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, + "node_modules/@types/http-proxy": { + "version": "1.17.15", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.15.tgz", + "integrity": "sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/jasmine": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.3.6.tgz", + "integrity": "sha512-3N0FpQTeiWjm+Oo1WUYWguUS7E6JLceiGTriFrG8k5PU7zRLJCzLcWURU3wjMbZGS//a2/LgjsnO3QxIlwxt9g==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "node_modules/@types/mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "22.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.12.0.tgz", + "integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==", + "dev": true, + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.9.18", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", + "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/wrap-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", + "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", + "dev": true + }, + "node_modules/@types/ws": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.14.tgz", + "integrity": "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.22.0.tgz", + "integrity": "sha512-4Uta6REnz/xEJMvwf72wdUnC3rr4jAQf5jnTkeRQ9b6soxLxhDEbS/pfMPoJLDfFPNVRdryqWUIV/2GZzDJFZw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.22.0", + "@typescript-eslint/type-utils": "8.22.0", + "@typescript-eslint/utils": "8.22.0", + "@typescript-eslint/visitor-keys": "8.22.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz", + "integrity": "sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/typescript-estree": "3.10.1", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/types": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", + "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==", + "dev": true, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", + "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/visitor-keys": "3.10.1", + "debug": "^4.1.1", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", + "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.22.0.tgz", + "integrity": "sha512-MqtmbdNEdoNxTPzpWiWnqNac54h8JDAmkWtJExBVVnSrSmi9z+sZUt0LfKqk9rjqmKOIeRhO4fHHJ1nQIjduIQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.22.0", + "@typescript-eslint/types": "8.22.0", + "@typescript-eslint/typescript-estree": "8.22.0", + "@typescript-eslint/visitor-keys": "8.22.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.22.0.tgz", + "integrity": "sha512-/lwVV0UYgkj7wPSw0o8URy6YI64QmcOdwHuGuxWIYznO6d45ER0wXUbksr9pYdViAofpUCNJx/tAzNukgvaaiQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.22.0", + "@typescript-eslint/visitor-keys": "8.22.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.22.0.tgz", + "integrity": "sha512-NzE3aB62fDEaGjaAYZE4LH7I1MUwHooQ98Byq0G0y3kkibPJQIXVUspzlFOmOfHhiDLwKzMlWxaNv+/qcZurJA==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "8.22.0", + "@typescript-eslint/utils": "8.22.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.22.0.tgz", + "integrity": "sha512-0S4M4baNzp612zwpD4YOieP3VowOARgK2EkN/GBn95hpyF8E2fbMT55sRHWBq+Huaqk3b3XK+rxxlM8sPgGM6A==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.22.0.tgz", + "integrity": "sha512-SJX99NAS2ugGOzpyhMza/tX+zDwjvwAtQFLsBo3GQxiGcvaKlqGBkmZ+Y1IdiSi9h4Q0Lr5ey+Cp9CGWNY/F/w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.22.0", + "@typescript-eslint/visitor-keys": "8.22.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.22.0.tgz", + "integrity": "sha512-T8oc1MbF8L+Bk2msAvCUzjxVB2Z2f+vXYfcucE2wOmYs7ZUwco5Ep0fYZw8quNwOiw9K8GYVL+Kgc2pETNTLOg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.22.0", + "@typescript-eslint/types": "8.22.0", + "@typescript-eslint/typescript-estree": "8.22.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.22.0.tgz", + "integrity": "sha512-AWpYAXnUgvLNabGTy3uBylkgZoosva/miNd1I8Bz3SjotmQPbVqhO4Cczo8AsZ44XVErEBPr/CRSgaj8sG7g0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.22.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true + }, + "node_modules/@vitejs/plugin-basic-ssl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz", + "integrity": "sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A==", + "dev": true, + "engines": { + "node": ">=14.6.0" + }, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/adjust-sourcemap-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", + "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/adjust-sourcemap-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/babel-loader": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", + "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==", + "dev": true, + "dependencies": { + "find-cache-dir": "^4.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz", + "integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.3", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz", + "integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.3" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/bonjour-service": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", + "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001696", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz", + "integrity": "sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clone-deep/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/commist": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/commist/-/commist-1.1.0.tgz", + "integrity": "sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==", + "dependencies": { + "leven": "^2.1.0", + "minimist": "^1.1.0" + } + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "dev": true + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.5.tgz", + "integrity": "sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.0.2", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", + "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", + "dev": true, + "dependencies": { + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.1", + "globby": "^14.0.0", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/core-js-compat": { + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.40.0.tgz", + "integrity": "sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==", + "dev": true, + "dependencies": { + "browserslist": "^4.24.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/critters": { + "version": "0.0.24", + "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.24.tgz", + "integrity": "sha512-Oyqew0FGM0wYUSNqR0L6AteO5MpMoUU0rhKRieXeiKs+PmRTxiJMyaunYB2KF6fQ3dzChXKCpbFOEJx3OQ1v/Q==", + "deprecated": "Ownership of Critters has moved to the Nuxt team, who will be maintaining the project going forward. If you'd like to keep using Critters, please switch to the actively-maintained fork at https://github.com/danielroe/beasties", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "css-select": "^5.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.2", + "htmlparser2": "^8.0.2", + "postcss": "^8.4.23", + "postcss-media-query-parser": "^0.2.3" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-loader": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", + "dev": true + }, + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", + "dev": true + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", + "dev": true, + "dependencies": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.5.90", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.90.tgz", + "integrity": "sha512-C3PN4aydfW91Natdyd449Kw+BzhLmof6tzy5W1pFC5SpQxVXT+oyiyOG9AgYYSN9OdA/ik3YkCrpwqI8ug5Tug==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "dev": true, + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", + "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.2.tgz", + "integrity": "sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "punycode": "^1.4.1", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "devOptional": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", + "dev": true + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", + "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.0", + "@esbuild/android-arm": "0.23.0", + "@esbuild/android-arm64": "0.23.0", + "@esbuild/android-x64": "0.23.0", + "@esbuild/darwin-arm64": "0.23.0", + "@esbuild/darwin-x64": "0.23.0", + "@esbuild/freebsd-arm64": "0.23.0", + "@esbuild/freebsd-x64": "0.23.0", + "@esbuild/linux-arm": "0.23.0", + "@esbuild/linux-arm64": "0.23.0", + "@esbuild/linux-ia32": "0.23.0", + "@esbuild/linux-loong64": "0.23.0", + "@esbuild/linux-mips64el": "0.23.0", + "@esbuild/linux-ppc64": "0.23.0", + "@esbuild/linux-riscv64": "0.23.0", + "@esbuild/linux-s390x": "0.23.0", + "@esbuild/linux-x64": "0.23.0", + "@esbuild/netbsd-x64": "0.23.0", + "@esbuild/openbsd-arm64": "0.23.0", + "@esbuild/openbsd-x64": "0.23.0", + "@esbuild/sunos-x64": "0.23.0", + "@esbuild/win32-arm64": "0.23.0", + "@esbuild/win32-ia32": "0.23.0", + "@esbuild/win32-x64": "0.23.0" + } + }, + "node_modules/esbuild-wasm": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.23.0.tgz", + "integrity": "sha512-6jP8UmWy6R6TUUV8bMuC3ZyZ6lZKI56x0tkxyCIqWwRRJ/DgeQKneh/Oid5EoGoPFLrGNkz47ZEtWAYuiY/u9g==", + "dev": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.3.tgz", + "integrity": "sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/exponential-backoff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", + "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", + "dev": true + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/express/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/fastq": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", + "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-cache-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", + "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "dev": true, + "dependencies": { + "common-path-prefix": "^3.0.0", + "pkg-dir": "^7.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", + "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", + "dev": true, + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/help-me": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-3.0.0.tgz", + "integrity": "sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ==", + "dependencies": { + "glob": "^7.1.6", + "readable-stream": "^3.6.0" + } + }, + "node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.9.tgz", + "integrity": "sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw==", + "dev": true + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-middleware": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.3.tgz", + "integrity": "sha512-usY0HG5nyDUwtqpiZdETNbmKtw3QQ1jwYFZ9wi5iHzX2BcILwQKtYDJPo7XHTsu5Z0B2Hj3W9NNnbd+AjFWjqg==", + "dev": true, + "dependencies": { + "@types/http-proxy": "^1.17.15", + "debug": "^4.3.6", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.3", + "is-plain-object": "^5.0.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz", + "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-walk": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", + "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", + "dev": true, + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immutable": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, + "node_modules/is-network-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", + "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jasmine-core": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.1.tgz", + "integrity": "sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==", + "dev": true + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/karma": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", + "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", + "dev": true, + "dependencies": { + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.7.2", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "bin": { + "karma": "bin/karma" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/karma-chrome-launcher": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz", + "integrity": "sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q==", + "dev": true, + "dependencies": { + "which": "^1.2.1" + } + }, + "node_modules/karma-chrome-launcher/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/karma-coverage": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.1.tgz", + "integrity": "sha512-yj7hbequkQP2qOSb20GuNSIyE//PgJWHwC2IydLE6XRtsnaflv+/OSGNssPjobYUlhVVagy99TQpqUt3vAUG7A==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.0.5", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/karma-coverage/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/karma-coverage/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/karma-coverage/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/karma-coverage/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/karma-jasmine": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.1.0.tgz", + "integrity": "sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ==", + "dev": true, + "dependencies": { + "jasmine-core": "^4.1.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "karma": "^6.0.0" + } + }, + "node_modules/karma-jasmine-html-reporter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.1.0.tgz", + "integrity": "sha512-sPQE1+nlsn6Hwb5t+HHwyy0A1FNCVKuL1192b+XNauMYWThz2kweiBVW1DqloRpVvZIJkIoHVB7XRpK78n1xbQ==", + "dev": true, + "peerDependencies": { + "jasmine-core": "^4.0.0 || ^5.0.0", + "karma": "^6.0.0", + "karma-jasmine": "^5.0.0" + } + }, + "node_modules/karma-source-map-support": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", + "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", + "dev": true, + "dependencies": { + "source-map-support": "^0.5.5" + } + }, + "node_modules/karma/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/karma/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/karma/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/karma/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/karma/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/karma/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/karma/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/karma/node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/karma/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/karma/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/karma/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/launch-editor": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.9.1.tgz", + "integrity": "sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==", + "dev": true, + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "node_modules/less": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz", + "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", + "dev": true, + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/less-loader": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-12.2.0.tgz", + "integrity": "sha512-MYUxjSQSBUQmowc0l5nPieOYwMzGPUaTzB6inNW/bdPEG9zOL3eAAD1Qw5ZxSPk7we5dMojHwNODYMV1hq4EVg==", + "dev": true, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "less": "^3.5.0 || ^4.0.0", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/less/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/less/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/less/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/less/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/license-webpack-plugin": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", + "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", + "dev": true, + "dependencies": { + "webpack-sources": "^3.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-sources": { + "optional": true + } + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/listr2": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.4.tgz", + "integrity": "sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==", + "dev": true, + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/lmdb": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.0.13.tgz", + "integrity": "sha512-UGe+BbaSUQtAMZobTb4nHvFMrmvuAQKSeaqAX2meTEQjfsbpl5sxdHD8T72OnwD4GU9uwNhYXIVe4QGs8N9Zyw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "msgpackr": "^1.10.2", + "node-addon-api": "^6.1.0", + "node-gyp-build-optional-packages": "5.2.2", + "ordered-binary": "^1.4.1", + "weak-lru-cache": "^1.2.2" + }, + "bin": { + "download-lmdb-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@lmdb/lmdb-darwin-arm64": "3.0.13", + "@lmdb/lmdb-darwin-x64": "3.0.13", + "@lmdb/lmdb-linux-arm": "3.0.13", + "@lmdb/lmdb-linux-arm64": "3.0.13", + "@lmdb/lmdb-linux-x64": "3.0.13", + "@lmdb/lmdb-win32-x64": "3.0.13" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", + "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", + "dev": true, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "dev": true, + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/log4js": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", + "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/loglevel": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "node_modules/loglevel-colored-level-prefix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz", + "integrity": "sha512-u45Wcxxc+SdAlh4yeF/uKlC1SPUPCy0gullSNKXod5I4bmifzk+Q4lSLExNEVn19tGaJipbZ4V4jbFn79/6mVA==", + "dev": true, + "dependencies": { + "chalk": "^1.1.3", + "loglevel": "^1.4.1" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-fetch-happen": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", + "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "dev": true, + "dependencies": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.0.tgz", + "integrity": "sha512-4eirfZ7thblFmqFjywlTmuWVSvccHAJbn1r8qQLzmTO11qcqpohOjmY2mFce6x7x7WtskzRqApPD0hv+Oa74jg==", + "dev": true, + "dependencies": { + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.3.0", + "tree-dump": "^1.0.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", + "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", + "dev": true, + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", + "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mqtt": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-4.3.7.tgz", + "integrity": "sha512-ew3qwG/TJRorTz47eW46vZ5oBw5MEYbQZVaEji44j5lAUSQSqIEoul7Kua/BatBW0H0kKQcC9kwUHa1qzaWHSw==", + "dependencies": { + "commist": "^1.0.0", + "concat-stream": "^2.0.0", + "debug": "^4.1.1", + "duplexify": "^4.1.1", + "help-me": "^3.0.0", + "inherits": "^2.0.3", + "lru-cache": "^6.0.0", + "minimist": "^1.2.5", + "mqtt-packet": "^6.8.0", + "number-allocator": "^1.0.9", + "pump": "^3.0.0", + "readable-stream": "^3.6.0", + "reinterval": "^1.1.0", + "rfdc": "^1.3.0", + "split2": "^3.1.0", + "ws": "^7.5.5", + "xtend": "^4.0.2" + }, + "bin": { + "mqtt": "bin/mqtt.js", + "mqtt_pub": "bin/pub.js", + "mqtt_sub": "bin/sub.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mqtt-browser": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/mqtt-browser/-/mqtt-browser-4.3.7.tgz", + "integrity": "sha512-4pxHxa3avIILr2CXhTKlArVpATqfyTu4zr5u2PoUwzgw0GDr5dpzZ0pmPgZyOoQBVgrVDEboCzb/b1Q0yWOm7g==", + "dependencies": { + "mqtt": "4.3.7" + } + }, + "node_modules/mqtt-packet": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-6.10.0.tgz", + "integrity": "sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA==", + "dependencies": { + "bl": "^4.0.2", + "debug": "^4.1.1", + "process-nextick-args": "^2.0.1" + } + }, + "node_modules/mqtt/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mqtt/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/mrmime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/msgpackr": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.2.tgz", + "integrity": "sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==", + "dev": true, + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" + } + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/needle": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "dev": true, + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/ngx-mask": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/ngx-mask/-/ngx-mask-16.4.2.tgz", + "integrity": "sha512-mQjcsTpctGu6HYKLf6/gjEUvW65D+46xvPIMYz0BDZXqHXrqKVluHXR3KF++TNOfdLLXwW6SvuHWd91NZN/C1A==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=14.0.0", + "@angular/core": ">=14.0.0", + "@angular/forms": ">=14.0.0" + } + }, + "node_modules/ngx-mqtt": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/ngx-mqtt/-/ngx-mqtt-17.0.0.tgz", + "integrity": "sha512-54wVMyDOZkpTZEs0rTMWPP1Yz+6q3rRnHzIBnpqnBkDcyMfNrti45C7ijwnEIaPDzQHMOqVrDgh/6C4ocPPLJQ==", + "dependencies": { + "mqtt-browser": "4.3.7", + "mqtt-packet": "^6.10.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=14", + "@angular/core": ">=14" + } + }, + "node_modules/nice-napi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", + "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "!win32" + ], + "dependencies": { + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.2" + } + }, + "node_modules/nice-napi/node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true, + "optional": true + }, + "node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.3.1.tgz", + "integrity": "sha512-Pp3nFHBThHzVtNY7U6JfPjvT/DTE8+o/4xKsLQtBoU+j2HLsGlhcfzflAoUreaJbNmYnX+LlLi0qjV8kpyO6xQ==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^4.1.0", + "semver": "^7.3.5", + "tar": "^6.2.1", + "which": "^4.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "dev": true, + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "dev": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/node-gyp/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true + }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-bundled": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", + "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "dev": true, + "dependencies": { + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-install-checks": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "dev": true, + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-packlist": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", + "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", + "dev": true, + "dependencies": { + "ignore-walk": "^6.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", + "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", + "dev": true, + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-17.1.0.tgz", + "integrity": "sha512-5+bKQRH0J1xG1uZ1zMNvxW0VEyoNWgJpY9UDuluPFLKDfJ9u2JmmjmTJV1srBGQOROfdBMiVvnH2Zvpbm+xkVA==", + "dev": true, + "dependencies": { + "@npmcli/redact": "^2.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^13.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minizlib": "^2.1.2", + "npm-package-arg": "^11.0.0", + "proc-log": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/number-allocator": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz", + "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==", + "dependencies": { + "debug": "^4.3.1", + "js-sdsl": "4.3.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "dev": true, + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/ordered-binary": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.2.tgz", + "integrity": "sha512-JTo+4+4Fw7FreyAvlSLjb1BBVaxEQAacmjD3jjuyPZclpbEghTvQZbXBb2qPd2LeIMxiHwXBZUcpmG2Gl/mDEA==", + "dev": true + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "dev": true, + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry/node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, + "node_modules/pacote": { + "version": "18.0.6", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-18.0.6.tgz", + "integrity": "sha512-+eK3G27SMwsB8kLIuj4h1FUhHtwiEUo21Tw8wNjmvdlpOEr613edv+8FUsTj/4F/VN5ywGE19X18N7CC2EJk6A==", + "dev": true, + "dependencies": { + "@npmcli/git": "^5.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/package-json": "^5.1.0", + "@npmcli/promise-spawn": "^7.0.0", + "@npmcli/run-script": "^8.0.0", + "cacache": "^18.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^11.0.0", + "npm-packlist": "^8.0.0", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^17.0.0", + "proc-log": "^4.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^2.2.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "devOptional": true, + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-html-rewriting-stream": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-7.0.0.tgz", + "integrity": "sha512-mazCyGWkmCRWDI15Zp+UiCqMp/0dgEmkZRvhlsqqKYr4SsVm/TvnSpD9fCvqCA2zoWJcfRym846ejWBBHRiYEg==", + "dev": true, + "dependencies": { + "entities": "^4.3.0", + "parse5": "^7.0.0", + "parse5-sax-parser": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-sax-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", + "integrity": "sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg==", + "dev": true, + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true + }, + "node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/piscina": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.6.1.tgz", + "integrity": "sha512-z30AwWGtQE+Apr+2WBZensP2lIvwoaMcOPkQlIEmSGMJNUvaYACylPYrQM6wSdUNJlnDVMSpLv7xTMJqlVshOA==", + "dev": true, + "optionalDependencies": { + "nice-napi": "^1.0.2" + } + }, + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "dev": true, + "dependencies": { + "find-up": "^6.3.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/pkg-dir/node_modules/yocto-queue": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/postcss": { + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-loader": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", + "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", + "dev": true, + "dependencies": { + "cosmiconfig": "^9.0.0", + "jiti": "^1.20.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-eslint": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-13.0.0.tgz", + "integrity": "sha512-P5K31qWgUOQCtJL/3tpvEe28KfP49qbr6MTVEXC7I2k7ci55bP3YDr+glhyCdhIzxGCVp2f8eobfQ5so52RIIA==", + "dev": true, + "dependencies": { + "@typescript-eslint/parser": "^3.0.0", + "common-tags": "^1.4.0", + "dlv": "^1.1.0", + "eslint": "^7.9.0", + "indent-string": "^4.0.0", + "lodash.merge": "^4.6.0", + "loglevel-colored-level-prefix": "^1.0.0", + "prettier": "^2.0.0", + "pretty-format": "^23.0.1", + "require-relative": "^0.8.7", + "typescript": "^3.9.3", + "vue-eslint-parser": "~7.1.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/prettier-eslint/node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/prettier-eslint/node_modules/@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/prettier-eslint/node_modules/@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/prettier-eslint/node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "node_modules/prettier-eslint/node_modules/@typescript-eslint/parser": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.10.1.tgz", + "integrity": "sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw==", + "dev": true, + "dependencies": { + "@types/eslint-visitor-keys": "^1.0.0", + "@typescript-eslint/experimental-utils": "3.10.1", + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/typescript-estree": "3.10.1", + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/prettier-eslint/node_modules/@typescript-eslint/types": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", + "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==", + "dev": true, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/prettier-eslint/node_modules/@typescript-eslint/typescript-estree": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", + "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/visitor-keys": "3.10.1", + "debug": "^4.1.1", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/prettier-eslint/node_modules/@typescript-eslint/visitor-keys": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", + "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/prettier-eslint/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/prettier-eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/prettier-eslint/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/prettier-eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/prettier-eslint/node_modules/eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "dependencies": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/prettier-eslint/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/prettier-eslint/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint/node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/prettier-eslint/node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/prettier-eslint/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/prettier-eslint/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prettier-eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prettier-eslint/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/prettier-eslint/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/prettier-eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/prettier-eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/prettier-eslint/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-eslint/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/prettier-eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prettier-eslint/node_modules/typescript": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", + "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", + "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0", + "ansi-styles": "^3.2.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pretty-format/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/pretty-format/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "optional": true + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true + }, + "node_modules/qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true, + "engines": { + "node": ">=0.9" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "dev": true + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regex-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.0.tgz", + "integrity": "sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg==", + "dev": true + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/regexpu-core": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true + }, + "node_modules/regjsparser": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "dev": true, + "dependencies": { + "jsesc": "~3.0.2" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/reinterval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz", + "integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-relative": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", + "integrity": "sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==", + "dev": true + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-url-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", + "integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==", + "dev": true, + "dependencies": { + "adjust-sourcemap-loader": "^4.0.0", + "convert-source-map": "^1.7.0", + "loader-utils": "^2.0.0", + "postcss": "^8.2.14", + "source-map": "0.6.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/resolve-url-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/resolve-url-loader/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", + "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.22.4", + "@rollup/rollup-android-arm64": "4.22.4", + "@rollup/rollup-darwin-arm64": "4.22.4", + "@rollup/rollup-darwin-x64": "4.22.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", + "@rollup/rollup-linux-arm-musleabihf": "4.22.4", + "@rollup/rollup-linux-arm64-gnu": "4.22.4", + "@rollup/rollup-linux-arm64-musl": "4.22.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", + "@rollup/rollup-linux-riscv64-gnu": "4.22.4", + "@rollup/rollup-linux-s390x-gnu": "4.22.4", + "@rollup/rollup-linux-x64-gnu": "4.22.4", + "@rollup/rollup-linux-x64-musl": "4.22.4", + "@rollup/rollup-win32-arm64-msvc": "4.22.4", + "@rollup/rollup-win32-ia32-msvc": "4.22.4", + "@rollup/rollup-win32-x64-msvc": "4.22.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/sass": { + "version": "1.77.6", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz", + "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-loader": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.0.tgz", + "integrity": "sha512-n13Z+3rU9A177dk4888czcVFiC8CL9dii4qpXWUg3YIIgZEvi9TCFKjOQcbK0kJM7DJu9VucrZFddvNfYCPwtw==", + "dev": true, + "dependencies": { + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "dev": true, + "optional": true + }, + "node_modules/schema-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sigstore": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz", + "integrity": "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "@sigstore/sign": "^2.3.2", + "@sigstore/tuf": "^2.3.4", + "@sigstore/verify": "^1.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dev": true, + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dev": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dev": true, + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", + "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", + "dev": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.72.1" + } + }, + "node_modules/source-map-loader/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", + "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "dev": true + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dependencies": { + "readable-stream": "^3.0.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true + }, + "node_modules/ssri": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", + "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==" + }, + "node_modules/streamroller": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", + "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/synckit": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", + "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/table/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/table/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/terser": { + "version": "5.31.6", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz", + "integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz", + "integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "dev": true, + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tree-dump": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz", + "integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-api-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", + "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", + "dev": true, + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tuf-js": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.1.tgz", + "integrity": "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==", + "dev": true, + "dependencies": { + "@tufjs/models": "2.0.1", + "debug": "^4.3.4", + "make-fetch-happen": "^13.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-assert": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", + "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", + "dev": true + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, + "node_modules/typescript": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.40", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.40.tgz", + "integrity": "sha512-us1E3K+3jJppDBa3Tl0L3MOJiGhe1C6P0+nIvQAFYbxlMAx0h81eOwLmU57xgqToduDDPx3y5QsdjPfDu+FgOQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", + "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", + "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", + "dev": true + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "5.4.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", + "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vite/node_modules/postcss": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", + "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vue-eslint-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.1.1.tgz", + "integrity": "sha512-8FdXi0gieEwh1IprIBafpiJWcApwrU+l2FEj8c1HtHFdNXMd0+2jUSjBVmcQYohf/E72irwAXEXLga6TQcB3FA==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "eslint-scope": "^5.0.0", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.2.1", + "esquery": "^1.0.1", + "lodash": "^4.17.15" + }, + "engines": { + "node": ">=8.10" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/vue-eslint-parser/node_modules/espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "dependencies": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/watchpack": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", + "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/weak-lru-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", + "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", + "dev": true + }, + "node_modules/webpack": { + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.7.1", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", + "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", + "dev": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.6.0", + "mime-types": "^2.1.31", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.0.4.tgz", + "integrity": "sha512-dljXhUgx3HqKP2d8J/fUMvhxGhzjeNVarDLcbO/EWMSgRizDkxHQDZQaLFL5VJY9tRBj2Gz+rvCEYYvhbqPHNA==", + "dev": true, + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.4.0", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "rimraf": "^5.0.5", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.1.0", + "ws": "^8.16.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", + "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", + "dev": true, + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-subresource-integrity": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz", + "integrity": "sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==", + "dev": true, + "dependencies": { + "typed-assert": "^1.0.8" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "html-webpack-plugin": ">= 5.0.0-beta.1 < 6", + "webpack": "^5.12.0" + }, + "peerDependenciesMeta": { + "html-webpack-plugin": { + "optional": true + } + } + }, + "node_modules/webpack/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/webpack/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/webpack/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zone.js": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz", + "integrity": "sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ==" + } + } +} diff --git a/resources/report/pilot-icon.png b/resources/report/pilot-icon.png deleted file mode 100644 index 8f9f9d0f83de46c5e3c1885044ee50d9a2db20ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 536 zcmV+z0_XjSP) z4)EM>$ZVEHB46_IX6C*5`DSKGd7uU1x?}1O2IM%7C%dF+8buaybetbD%{OYcS{y_W zjYf&oKd9ABwXwOSR#w;4QNEzs?T)f-Tb0VUY>R|T>{u3Qu^8#PPKNP9buY87uhY}x z1K)zNM*(^ySZ4mXz;_U5rzez1>~mn0tZ6Z}_xl4%93&{m3w}J zjFU`dm{z0NV!hqA6p1qo!{L`73`c@X0NBFs^jtNjKIC!*mhn;0XtdOM@q!Z+y&Lbn zUVjomvfz_cBBh2=zPaP~s+UwSGe)VX?Dau_1X5WkUo+%?D7_Ve!Tme=mWgMT!VoAS7x6=t!DBkG;SW| aIRn2q^;07Y2BMk(00005?`VC(=ph$UD7u><_APOgL$z_9M!UeJ)3A#WygAKCYA z-|ZeT^9)Eb06Ja^an5}gewKiey^ePn1p;#lFOo!Z4WTEDgR=}wv3BmWxQ1nxgWcM% zCm4Z1_LeEB^z6+jfiu}GAAsYoSAY$(L9!h0wG3Sgo(Y=z6!(qD5V<3uKO6+_A=pHC zh}>&IYnwsjHHuW1WajyjZ_-NpAl`w9)6Q!7jv_B%At>7?6UQ&CeILB(mjlz4j>R7l zU#1N@kcT)aqlJl)2;1O!0DQa1HEv~Z;mZWH0}qxig$n#9!fE=baXU+b*3CYh1N@~B z<+e)&PV!+sMau}E_R`3*ZeC(bz(f`q=(M#+Jhf{ W?v;VYPHlbw0000