diff --git a/C/1dvelocitysimulator/Makefile b/C/1dvelocitysimulator/Makefile index 1fce470..f13c403 100644 --- a/C/1dvelocitysimulator/Makefile +++ b/C/1dvelocitysimulator/Makefile @@ -1,19 +1,47 @@ CC := gcc CFLAGS := -O2 -Wall -Wextra -std=c11 -D_DEFAULT_SOURCE -LDFLAGS := +LDFLAGS := -lm -SRC := main.c +SRC := main.c physics.c BIN := 1dvelocitysimulator +TEST_SRC := test_physics.c physics.c +TEST_BIN := test_physics + +COV_CFLAGS := -Wall -Wextra -std=c11 -D_DEFAULT_SOURCE -DTESTING --coverage -g -O0 +COV_LDFLAGS := -lm + all: $(BIN) -$(BIN): $(SRC) - $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) +$(BIN): $(SRC) physics.h + $(CC) $(CFLAGS) -o $@ $(SRC) $(LDFLAGS) run: $(BIN) ./$(BIN) -clean: - rm -f $(BIN) +test: $(TEST_BIN) + ./$(TEST_BIN) -.PHONY: all run clean +$(TEST_BIN): $(TEST_SRC) physics.h + $(CC) $(CFLAGS) -DTESTING -o $@ $(TEST_SRC) $(LDFLAGS) + +coverage: + $(CC) $(COV_CFLAGS) -o $(TEST_BIN) $(TEST_SRC) $(COV_LDFLAGS) + ./$(TEST_BIN) + lcov --capture --directory . --output-file coverage.info --rc branch_coverage=1 + lcov --remove coverage.info '/usr/*' --output-file coverage.info \ + --rc branch_coverage=1 --ignore-errors unused + @LINE_PCT=$$(lcov --summary coverage.info 2>&1 | grep -oP 'lines\.*:\s*\K[0-9.]+'); \ + echo "Line coverage: $${LINE_PCT}%"; \ + if [ "$$(echo "$${LINE_PCT} < 100.0" | bc -l)" = "1" ]; then \ + echo "FAIL: Line coverage $${LINE_PCT}% is below 100%"; \ + exit 1; \ + else \ + echo "OK: 100% line coverage achieved"; \ + fi + +clean: + rm -f $(BIN) $(TEST_BIN) *.gcda *.gcno *.gcov coverage.info + rm -rf coverage_html + +.PHONY: all run test coverage clean diff --git a/C/1dvelocitysimulator/main.c b/C/1dvelocitysimulator/main.c index b5ea54a..162c40e 100644 --- a/C/1dvelocitysimulator/main.c +++ b/C/1dvelocitysimulator/main.c @@ -1,180 +1,4 @@ -#include -#include -#include - -#ifdef _WIN32 -#include -#define SLEEP_MS(ms) Sleep(ms) -#define CLEAR_SCREEN() system("CLS") -#define PAUSE() system("PAUSE") -#else -#include -#define SLEEP_MS(ms) usleep((ms) * 1000U) -#define CLEAR_SCREEN() system("clear") -#define PAUSE() \ - do \ - { \ - printf("Press Enter to continue..."); \ - getchar(); \ - } while (0) -#endif - -#define LINE_LENGTH 100 - -void C() -{ - printf("\nCheck\n"); - return; -} - -void printAcceleration(int acceleration) -{ - printf("The value of acceleration is: %d\n", acceleration); - PAUSE(); - return; -} - -void pauseSystem() { PAUSE(); } - -void clearScreen() -{ - CLEAR_SCREEN(); - return; -} - -void pauseForASecond() -{ - SLEEP_MS(1000); - return; -} - -void pauseForGivenTime(float given_time) -{ - SLEEP_MS((unsigned int)fabs(given_time * 1000)); - return; -} - -float calculateVelocity(float starting_velocity, unsigned int physics_time, int *acceleration) -{ - // cppcheck-suppress nullPointer - return (*acceleration) * physics_time + starting_velocity; -} - -int calculateDisplacement(float starting_velocity, int *acceleration, unsigned int physics_time) -{ - // cppcheck-suppress nullPointer - // cppcheck-suppress ctunullpointer - return starting_velocity * physics_time + ((1 / 2) * (*acceleration) * (physics_time ^ 2)); -} - -void printXPosition(int position) -{ - printf("\nx position is: %d\n", position); - return; -} - -void printClock(unsigned int *time) -{ - printf("%u seconds passed\n", *time); - return; -} - -float calculateStopTime(float velocity) { return 1 / velocity; } - -void printLine(int position) -{ - clearScreen(); - for (int i = -(LINE_LENGTH / 2); i < LINE_LENGTH / 2; i++) - { - if (i == position) - printf("x"); - else - printf("-"); - } - return; -} - -void printVelocity(float velocity) -{ - printf("Velocity is: %f\n", velocity); - return; -} - -int calculateTimePassed(float velocity) -{ - if (velocity >= 1 || velocity <= -1) - return 1; - else - { - printf("Time passed is: %f\n", fabs(1 / velocity)); - return fabs(1 / velocity); - } -} - -void printAllInfo(int position, unsigned int *time, float *velocity) -{ - pauseForGivenTime(calculateStopTime(*velocity)); - printLine(position); - printXPosition(position); - *time += calculateTimePassed(*velocity); - printClock(time); - printVelocity(*velocity); - // pauseForASecond(); - return; -} - -float chooseVelocity() -{ - float velocity; - printf("Write velocity of the object in m / s: "); - scanf("%f", &velocity); - return velocity; -} - -int chooseAcceleration() -{ - int acceleration; - printf("Choose acceleration of the object in m / (s ^ 2):"); - scanf("%d", &acceleration); - return acceleration; -} - -int outOfLine(int position) -{ - if ((position < LINE_LENGTH / 2) && (position > -1 * (LINE_LENGTH / 2))) - { - return 0; - } - else - return 1; -} - -void moveUntillOutOfLine(int position, unsigned int *time) -{ - while (!outOfLine(position)) - { - float velocity = chooseVelocity(); - float *Pvelocity = &velocity; - position += calculateDisplacement(velocity, 0, 1); - printAllInfo(position, time, Pvelocity); - } - return; -} - -void moveUntillOutOfVelocity(int position, int *acceleration, unsigned int *time) -{ - float velocity = 0; - float *Pvelocity = &velocity; - while (!outOfLine(position)) - { - position += calculateDisplacement(velocity, acceleration, 1); - printXPosition(position); - pauseSystem(); - velocity = calculateVelocity(velocity, 1, acceleration); - printAllInfo(position, time, Pvelocity); - } - return; -} +#include "physics.h" int main() { diff --git a/C/1dvelocitysimulator/physics.c b/C/1dvelocitysimulator/physics.c new file mode 100644 index 0000000..418d8f8 --- /dev/null +++ b/C/1dvelocitysimulator/physics.c @@ -0,0 +1,160 @@ +#include "physics.h" + +#include +#include +#include + +void C() +{ + printf("\nCheck\n"); + return; +} + +void printAcceleration(int acceleration) +{ + printf("The value of acceleration is: %d\n", acceleration); + PAUSE(); + return; +} + +void pauseSystem() { PAUSE(); } + +void clearScreen() +{ + CLEAR_SCREEN(); + return; +} + +void pauseForASecond() +{ + SLEEP_MS(1000); + return; +} + +void pauseForGivenTime(float given_time) +{ + SLEEP_MS((unsigned int)fabs((double)given_time * 1000)); + return; +} + +float calculateVelocity(float starting_velocity, unsigned int physics_time, int *acceleration) +{ + // cppcheck-suppress nullPointer + return (*acceleration) * physics_time + starting_velocity; +} + +int calculateDisplacement(float starting_velocity, int *acceleration, unsigned int physics_time) +{ + // cppcheck-suppress nullPointer + // cppcheck-suppress ctunullpointer + return starting_velocity * physics_time + ((1 / 2) * (*acceleration) * (physics_time ^ 2)); +} + +void printXPosition(int position) +{ + printf("\nx position is: %d\n", position); + return; +} + +void printClock(unsigned int *time) +{ + printf("%u seconds passed\n", *time); + return; +} + +float calculateStopTime(float velocity) { return 1 / velocity; } + +void printLine(int position) +{ + clearScreen(); + for (int i = -(LINE_LENGTH / 2); i < LINE_LENGTH / 2; i++) + { + if (i == position) + printf("x"); + else + printf("-"); + } + return; +} + +void printVelocity(float velocity) +{ + printf("Velocity is: %f\n", velocity); + return; +} + +int calculateTimePassed(float velocity) +{ + if (velocity >= 1 || velocity <= -1) + return 1; + else + { + printf("Time passed is: %f\n", fabs(1 / velocity)); + return fabs(1 / velocity); + } +} + +void printAllInfo(int position, unsigned int *time, float *velocity) +{ + pauseForGivenTime(calculateStopTime(*velocity)); + printLine(position); + printXPosition(position); + *time += calculateTimePassed(*velocity); + printClock(time); + printVelocity(*velocity); + // pauseForASecond(); + return; +} + +float chooseVelocity() +{ + float velocity; + printf("Write velocity of the object in m / s: "); + scanf("%f", &velocity); + return velocity; +} + +int chooseAcceleration() +{ + int acceleration; + printf("Choose acceleration of the object in m / (s ^ 2):"); + scanf("%d", &acceleration); + return acceleration; +} + +int outOfLine(int position) +{ + if ((position < LINE_LENGTH / 2) && (position > -1 * (LINE_LENGTH / 2))) + { + return 0; + } + else + return 1; +} + +void moveUntillOutOfLine(int position, unsigned int *time) +{ + while (!outOfLine(position)) + { + float velocity = chooseVelocity(); + float *Pvelocity = &velocity; + position += calculateDisplacement(velocity, 0, 1); + printAllInfo(position, time, Pvelocity); + } + return; +} + +void moveUntillOutOfVelocity(int position, int *acceleration, unsigned int *time) +{ + float velocity = 0; + float *Pvelocity = &velocity; + while (!outOfLine(position)) + { + position += calculateDisplacement(velocity, acceleration, 1); + printXPosition(position); + pauseSystem(); + velocity = calculateVelocity(velocity, 1, acceleration); + printAllInfo(position, time, Pvelocity); + } + return; +} diff --git a/C/1dvelocitysimulator/physics.h b/C/1dvelocitysimulator/physics.h new file mode 100644 index 0000000..254cf5f --- /dev/null +++ b/C/1dvelocitysimulator/physics.h @@ -0,0 +1,55 @@ +#ifndef PHYSICS_H +#define PHYSICS_H + +#ifdef _WIN32 +#include +#define SLEEP_MS(ms) Sleep(ms) +#define CLEAR_SCREEN() system("CLS") +#define PAUSE() \ + do \ + { \ + printf("Press Enter to continue..."); \ + getchar(); \ + } while (0) +#else +#include +#ifdef TESTING +#define SLEEP_MS(ms) ((void)0) +#define CLEAR_SCREEN() ((void)0) +#define PAUSE() ((void)0) +#else +#define SLEEP_MS(ms) usleep((ms) * 1000U) +#define CLEAR_SCREEN() system("clear") +#define PAUSE() \ + do \ + { \ + printf("Press Enter to continue..."); \ + getchar(); \ + } while (0) +#endif +#endif + +#define LINE_LENGTH 100 + +void C(void); +void printAcceleration(int acceleration); +void pauseSystem(void); +void clearScreen(void); +void pauseForASecond(void); +void pauseForGivenTime(float given_time); +float calculateVelocity(float starting_velocity, unsigned int physics_time, int *acceleration); +int calculateDisplacement(float starting_velocity, int *acceleration, unsigned int physics_time); +void printXPosition(int position); +void printClock(unsigned int *time); +float calculateStopTime(float velocity); +void printLine(int position); +void printVelocity(float velocity); +int calculateTimePassed(float velocity); +void printAllInfo(int position, unsigned int *time, float *velocity); +float chooseVelocity(void); +int chooseAcceleration(void); +int outOfLine(int position); +void moveUntillOutOfLine(int position, unsigned int *time); +void moveUntillOutOfVelocity(int position, int *acceleration, unsigned int *time); + +#endif /* PHYSICS_H */ diff --git a/C/1dvelocitysimulator/test_physics.c b/C/1dvelocitysimulator/test_physics.c new file mode 100644 index 0000000..b7d4589 --- /dev/null +++ b/C/1dvelocitysimulator/test_physics.c @@ -0,0 +1,468 @@ +#include +#include +#include +#include +#include + +#include "physics.h" + +static void test_calculateVelocity(void) +{ + int accel = 2; + float v = calculateVelocity(5.0f, 3, &accel); + assert(fabsf(v - 11.0f) < 0.001f); + + /* acceleration=0: velocity unchanged */ + int accel2 = 0; + float v2 = calculateVelocity(10.0f, 5, &accel2); + assert(fabsf(v2 - 10.0f) < 0.001f); + + int accel3 = 0; + float v3 = calculateVelocity(3.0f, 10, &accel3); + assert(fabsf(v3 - 3.0f) < 0.001f); + + /* time=0: velocity equals starting velocity regardless of accel */ + int accel4 = 100; + float v4 = calculateVelocity(7.0f, 0, &accel4); + assert(fabsf(v4 - 7.0f) < 0.001f); +} + +static void test_calculateDisplacement(void) +{ + int accel = 2; + int d = calculateDisplacement(5.0f, &accel, 3); + /* With integer division (1/2)==0, result is starting_velocity * time + 0 */ + assert(d == 15); + + int accel2 = 0; + int d2 = calculateDisplacement(0.0f, &accel2, 10); + assert(d2 == 0); +} + +static void test_calculateStopTime(void) +{ + float t = calculateStopTime(2.0f); + assert(fabsf(t - 0.5f) < 0.001f); + + float t2 = calculateStopTime(0.5f); + assert(fabsf(t2 - 2.0f) < 0.001f); +} + +static void test_calculateTimePassed_fast(void) +{ + int t = calculateTimePassed(2.0f); + assert(t == 1); + + int t2 = calculateTimePassed(-5.0f); + assert(t2 == 1); + + int t3 = calculateTimePassed(1.0f); + assert(t3 == 1); + + int t4 = calculateTimePassed(-1.0f); + assert(t4 == 1); +} + +static void test_calculateTimePassed_slow(void) +{ + /* velocity between -1 and 1 (exclusive) takes the else branch */ + int t = calculateTimePassed(0.5f); + assert(t == (int)fabsf(1.0f / 0.5f)); + + int t2 = calculateTimePassed(0.25f); + assert(t2 == (int)fabsf(1.0f / 0.25f)); + + int t3 = calculateTimePassed(-0.5f); + assert(t3 == (int)fabsf(1.0f / -0.5f)); +} + +static void test_outOfLine(void) +{ + assert(outOfLine(0) == 0); + assert(outOfLine(10) == 0); + assert(outOfLine(-10) == 0); + assert(outOfLine(49) == 0); + assert(outOfLine(-49) == 0); + + /* at boundary and beyond */ + assert(outOfLine(50) == 1); + assert(outOfLine(-50) == 1); + assert(outOfLine(100) == 1); + assert(outOfLine(-100) == 1); +} + +static void test_C_function(void) +{ + { + FILE *_redir = freopen("/dev/null", "w", stdout); + assert(_redir != NULL); + (void)_redir; + } + C(); + { + FILE *_restore = freopen("/dev/tty", "w", stdout); + assert(_restore != NULL); + (void)_restore; + } +} + +static void test_printAcceleration(void) +{ + { + FILE *_redir = freopen("/dev/null", "w", stdout); + assert(_redir != NULL); + (void)_redir; + } + printAcceleration(5); + printAcceleration(-3); + { + FILE *_restore = freopen("/dev/tty", "w", stdout); + assert(_restore != NULL); + (void)_restore; + } +} + +static void test_pauseSystem(void) +{ + { + FILE *_redir = freopen("/dev/null", "w", stdout); + assert(_redir != NULL); + (void)_redir; + } + pauseSystem(); + { + FILE *_restore = freopen("/dev/tty", "w", stdout); + assert(_restore != NULL); + (void)_restore; + } +} + +static void test_clearScreen(void) { clearScreen(); } + +static void test_pauseForASecond(void) { pauseForASecond(); } + +static void test_pauseForGivenTime(void) +{ + pauseForGivenTime(0.5f); + pauseForGivenTime(-0.5f); + pauseForGivenTime(0.0f); +} + +static void test_printXPosition(void) +{ + { + FILE *_redir = freopen("/dev/null", "w", stdout); + assert(_redir != NULL); + (void)_redir; + } + printXPosition(0); + printXPosition(42); + printXPosition(-10); + { + FILE *_restore = freopen("/dev/tty", "w", stdout); + assert(_restore != NULL); + (void)_restore; + } +} + +static void test_printClock(void) +{ + { + FILE *_redir = freopen("/dev/null", "w", stdout); + assert(_redir != NULL); + (void)_redir; + } + unsigned int t = 10; + printClock(&t); + t = 0; + printClock(&t); + { + FILE *_restore = freopen("/dev/tty", "w", stdout); + assert(_restore != NULL); + (void)_restore; + } +} + +static void test_printVelocity(void) +{ + { + FILE *_redir = freopen("/dev/null", "w", stdout); + assert(_redir != NULL); + (void)_redir; + } + printVelocity(3.14f); + printVelocity(-1.0f); + { + FILE *_restore = freopen("/dev/tty", "w", stdout); + assert(_restore != NULL); + (void)_restore; + } +} + +static void test_printLine(void) +{ + /* Capture output to a temp file to verify content */ + FILE *tmp = tmpfile(); + assert(tmp != NULL); + int fd = fileno(tmp); + FILE *cap = fdopen(fd, "w+"); + /* Redirect stdout to tmp */ + FILE *saved = stdout; + stdout = cap; + + printLine(0); + fflush(stdout); + + stdout = saved; + + /* Read back and verify "x" at the correct position */ + fseek(cap, 0, SEEK_END); + long len = ftell(cap); + fseek(cap, 0, SEEK_SET); + char *buf = malloc(len + 1); + assert(buf != NULL); + fread(buf, 1, len, cap); + buf[len] = '\0'; + + /* The output is LINE_LENGTH characters. Position 0 maps to index 50 */ + assert(len == LINE_LENGTH); + assert(buf[50] == 'x'); + for (int i = 0; i < LINE_LENGTH; i++) + { + if (i != 50) + assert(buf[i] == '-'); + } + free(buf); + fclose(cap); +} + +static void test_printLine_edge(void) +{ + FILE *saved = stdout; + FILE *tmp = tmpfile(); + assert(tmp != NULL); + stdout = tmp; + printLine(-50); + fflush(stdout); + stdout = saved; + + fseek(tmp, 0, SEEK_END); + long len = ftell(tmp); + fseek(tmp, 0, SEEK_SET); + char *buf = malloc(len + 1); + assert(buf != NULL); + fread(buf, 1, len, tmp); + buf[len] = '\0'; + + /* position -50 maps to index 0 */ + assert(buf[0] == 'x'); + free(buf); + fclose(tmp); +} + +static void test_printAllInfo(void) +{ + { + FILE *_redir = freopen("/dev/null", "w", stdout); + assert(_redir != NULL); + (void)_redir; + } + unsigned int t = 0; + float vel = 2.0f; + printAllInfo(0, &t, &vel); + assert(t > 0); + + /* slow velocity branch */ + float vel2 = 0.5f; + unsigned int t2 = 0; + printAllInfo(10, &t2, &vel2); + assert(t2 > 0); + { + FILE *_restore = freopen("/dev/tty", "w", stdout); + assert(_restore != NULL); + (void)_restore; + } +} + +static void test_chooseVelocity(void) +{ + /* Redirect stdin to provide input */ + FILE *tmp_in = tmpfile(); + assert(tmp_in != NULL); + fprintf(tmp_in, "3.5\n"); + fseek(tmp_in, 0, SEEK_SET); + + FILE *saved_in = stdin; + stdin = tmp_in; + + { + FILE *_redir = freopen("/dev/null", "w", stdout); + assert(_redir != NULL); + (void)_redir; + } + + float v = chooseVelocity(); + assert(fabsf(v - 3.5f) < 0.001f); + + stdin = saved_in; + fclose(tmp_in); + { + FILE *_restore = freopen("/dev/tty", "w", stdout); + assert(_restore != NULL); + (void)_restore; + } +} + +static void test_chooseAcceleration(void) +{ + FILE *tmp_in = tmpfile(); + assert(tmp_in != NULL); + fprintf(tmp_in, "7\n"); + fseek(tmp_in, 0, SEEK_SET); + + FILE *saved_in = stdin; + stdin = tmp_in; + + { + FILE *_redir = freopen("/dev/null", "w", stdout); + assert(_redir != NULL); + (void)_redir; + } + + int a = chooseAcceleration(); + assert(a == 7); + + stdin = saved_in; + fclose(tmp_in); + { + FILE *_restore = freopen("/dev/tty", "w", stdout); + assert(_restore != NULL); + (void)_restore; + } +} + +static void test_moveUntillOutOfLine_already_out(void) +{ + /* Position already out of line: while loop body never executes */ + { + FILE *_redir = freopen("/dev/null", "w", stdout); + assert(_redir != NULL); + (void)_redir; + } + unsigned int t = 0; + moveUntillOutOfLine(999, &t); + assert(t == 0); + { + FILE *_restore = freopen("/dev/tty", "w", stdout); + assert(_restore != NULL); + (void)_restore; + } +} + +static void test_moveUntillOutOfLine_exits(void) +{ + /* + * Position starts in-line (0). Feed a large velocity via stdin so + * calculateDisplacement moves position out of line in one step. + * chooseVelocity reads a float; we feed "100\n". + */ + FILE *tmp_in = tmpfile(); + assert(tmp_in != NULL); + fprintf(tmp_in, "100\n"); + fseek(tmp_in, 0, SEEK_SET); + + FILE *saved_in = stdin; + stdin = tmp_in; + + { + FILE *_redir = freopen("/dev/null", "w", stdout); + assert(_redir != NULL); + (void)_redir; + } + + unsigned int t = 0; + moveUntillOutOfLine(0, &t); + + stdin = saved_in; + fclose(tmp_in); + { + FILE *_restore = freopen("/dev/tty", "w", stdout); + assert(_restore != NULL); + (void)_restore; + } +} + +static void test_moveUntillOutOfVelocity_already_out(void) +{ + { + FILE *_redir = freopen("/dev/null", "w", stdout); + assert(_redir != NULL); + (void)_redir; + } + int accel = 5; + unsigned int t = 0; + moveUntillOutOfVelocity(999, &accel, &t); + assert(t == 0); + { + FILE *_restore = freopen("/dev/tty", "w", stdout); + assert(_restore != NULL); + (void)_restore; + } +} + +static void test_moveUntillOutOfVelocity_runs(void) +{ + /* + * Start at position 49 (near boundary) with positive acceleration. + * velocity starts at 0, first iteration: displacement = 0*1 + 0 = 0, position + * stays 49. velocity becomes accel*1+0 = 10. Second iteration: displacement = + * 10*1 + 0 = 10, position = 59 -> out of line. We need at most a few iterations. + * Since chooseVelocity/chooseAcceleration are NOT called in this function, no + * stdin redirect needed. + */ + { + FILE *_redir = freopen("/dev/null", "w", stdout); + assert(_redir != NULL); + (void)_redir; + } + int accel = 10; + unsigned int t = 0; + moveUntillOutOfVelocity(49, &accel, &t); + assert(t > 0); + { + FILE *_restore = freopen("/dev/tty", "w", stdout); + assert(_restore != NULL); + (void)_restore; + } +} + +int main(void) +{ + test_calculateVelocity(); + test_calculateDisplacement(); + test_calculateStopTime(); + test_calculateTimePassed_fast(); + test_calculateTimePassed_slow(); + test_outOfLine(); + test_C_function(); + test_printAcceleration(); + test_pauseSystem(); + test_clearScreen(); + test_pauseForASecond(); + test_pauseForGivenTime(); + test_printXPosition(); + test_printClock(); + test_printVelocity(); + test_printLine(); + test_printLine_edge(); + test_printAllInfo(); + test_chooseVelocity(); + test_chooseAcceleration(); + test_moveUntillOutOfLine_already_out(); + test_moveUntillOutOfLine_exits(); + test_moveUntillOutOfVelocity_already_out(); + test_moveUntillOutOfVelocity_runs(); + + printf("All tests passed!\n"); + return 0; +} diff --git a/C/lichess_random_engine/Makefile b/C/lichess_random_engine/Makefile index 10efc71..94cf7fb 100644 --- a/C/lichess_random_engine/Makefile +++ b/C/lichess_random_engine/Makefile @@ -1,5 +1,6 @@ CC := gcc CFLAGS := -O2 -std=c11 -Wall -Wextra -Wno-unused-parameter +COV := -O0 -g --coverage -std=c11 -Wall -Wextra -Wno-unused-parameter -Wno-return-type LDFLAGS := SRC := main.c movegen.c search.c @@ -9,7 +10,7 @@ BIN := random_engine PERFT_SRC := perft.c movegen.c PERFT_BIN := perft -.PHONY: all clean rebuild +.PHONY: all clean rebuild test coverage all: $(BIN) @@ -19,8 +20,44 @@ $(BIN): $(SRC) $(PERFT_BIN): $(PERFT_SRC) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) +# ---- tests ------------------------------------------------------------------ + +test_movegen: test_movegen.c movegen.c movegen.h + $(CC) $(COV) -o test_movegen test_movegen.c movegen.c + +test_search: test_search.c search.c movegen.c movegen.h search.h + $(CC) $(COV) -o test_search test_search.c search.c movegen.c + +test: test_movegen test_search + ./test_movegen + ./test_search + +# ---- coverage --------------------------------------------------------------- + +coverage: test_movegen test_search + ./test_movegen + ./test_search + lcov --capture --directory . --output-file coverage.info \ + --rc branch_coverage=1 --ignore-errors inconsistent,mismatch,unused + lcov --extract coverage.info \ + "$(CURDIR)/movegen.c" "$(CURDIR)/search.c" \ + --output-file coverage.info \ + --ignore-errors unused,inconsistent + @echo "--- Coverage Summary ---" + lcov --summary coverage.info --rc branch_coverage=1 2>&1 | tee /tmp/lcov_engine_summary.txt + @LINE_COV=$$(grep "lines" /tmp/lcov_engine_summary.txt | grep -oP '[0-9]+\.[0-9]+(?=%)'); \ + echo "Line coverage: $${LINE_COV}%"; \ + if [ "$$(echo "$${LINE_COV} < 100.0" | bc)" = "1" ]; then \ + echo "FAIL: line coverage below 100%"; exit 1; \ + fi + @echo "OK: 100% line coverage achieved" + +run: $(BIN) + ./$(BIN) + clean: - rm -f $(BIN) $(PERFT_BIN) + rm -f $(BIN) $(PERFT_BIN) test_movegen test_search \ + *.gcda *.gcno *.gcov coverage.info rebuild: clean all diff --git a/C/lichess_random_engine/movegen.c b/C/lichess_random_engine/movegen.c index b07a470..72e2da4 100644 --- a/C/lichess_random_engine/movegen.c +++ b/C/lichess_random_engine/movegen.c @@ -45,39 +45,6 @@ static Piece make_piece(char c) } } -static char piece_to_char(Piece p) -{ - switch (p) - { - case WP: - return 'P'; - case WN: - return 'N'; - case WB: - return 'B'; - case WR: - return 'R'; - case WQ: - return 'Q'; - case WK: - return 'K'; - case BP: - return 'p'; - case BN: - return 'n'; - case BB: - return 'b'; - case BR: - return 'r'; - case BQ: - return 'q'; - case BK: - return 'k'; - default: - return '.'; - } -} - void set_startpos(Position *pos) { memset(pos, 0, sizeof(*pos)); @@ -707,8 +674,6 @@ static int gen_moves_internal(const Position *pos, Move *moves, int max_moves, i } } break; - default: - break; } } diff --git a/C/lichess_random_engine/perft b/C/lichess_random_engine/perft deleted file mode 100755 index 8546b95..0000000 Binary files a/C/lichess_random_engine/perft and /dev/null differ diff --git a/C/lichess_random_engine/search.c b/C/lichess_random_engine/search.c index 646817d..7bc4247 100644 --- a/C/lichess_random_engine/search.c +++ b/C/lichess_random_engine/search.c @@ -5,13 +5,11 @@ static int piece_value(Piece p) { - if (p == WK || p == BK) - { - return 0; // king is invaluable; PST handled later if needed - } - switch (p) { + case WK: + case BK: + return 0; /* king is invaluable; PST handled later if needed */ case WP: case BP: return 100; @@ -43,11 +41,7 @@ int evaluate(const Position *pos) continue; } Piece p = pos->board[sq]; - if (p == EMPTY) - { - continue; - } - int v = piece_value(p); + int v = piece_value(p); if (p >= WP && p <= WK) { score += v; diff --git a/C/lichess_random_engine/test_movegen.c b/C/lichess_random_engine/test_movegen.c new file mode 100644 index 0000000..0c94a9f --- /dev/null +++ b/C/lichess_random_engine/test_movegen.c @@ -0,0 +1,1440 @@ +/* + * test_movegen.c - Unit tests for movegen.c (parse_fen, make_move, unmake_move, + * gen_moves, in_check, square_from_algebraic, move_from_uci). + */ + +#include "movegen.h" + +#include +#include +#include + +/* Helper: count moves matching a given to-square */ +static int count_moves_to(Move *moves, int n, int to) +{ + int c = 0; + for (int i = 0; i < n; i++) + { + if (moves[i].to == to) + { + c++; + } + } + return c; +} + +/* Helper: find a move from→to in the move list; returns index or -1 */ +static int find_move(Move *moves, int n, int from, int to) +{ + for (int i = 0; i < n; i++) + { + if (moves[i].from == from && moves[i].to == to) + { + return i; + } + } + return -1; +} + +/* ========================================================================= + * parse_fen tests + * ========================================================================= */ + +static void test_parse_fen_startpos(void) +{ + Position pos; + int ok = parse_fen(&pos, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); + assert(ok); + assert(pos.side == WHITE); + assert(pos.castle == 0xF); /* all four castling rights */ + assert(pos.ep_square == -1); + assert(pos.halfmove_clock == 0); + assert(pos.fullmove_number == 1); + + /* a1 = 0x00 should be WR */ + assert(pos.board[0x00] == WR); + /* e1 = 0x04 WK */ + assert(pos.board[0x04] == WK); + /* e8 = 0x74 BK */ + assert(pos.board[0x74] == BK); + /* a2 = 0x10 WP */ + assert(pos.board[0x10] == WP); + /* e5 = 0x44 EMPTY */ + assert(pos.board[0x44] == EMPTY); +} + +static void test_parse_fen_black_to_move(void) +{ + Position pos; + int ok = parse_fen(&pos, "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"); + assert(ok); + assert(pos.side == BLACK); + /* e3 = rank1+2, file e=4 => 0x24 */ + assert(pos.ep_square == 0x24); +} + +static void test_parse_fen_no_castling(void) +{ + Position pos; + int ok = parse_fen(&pos, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1"); + assert(ok); + assert(pos.castle == 0); +} + +static void test_parse_fen_partial_castling(void) +{ + Position pos; + int ok = parse_fen(&pos, "r3k2r/8/8/8/8/8/8/R3K2R w Kq - 0 1"); + assert(ok); + assert(pos.castle & (1 << 0)); /* white kingside */ + assert(!(pos.castle & (1 << 1))); /* not white queenside */ + assert(!(pos.castle & (1 << 2))); /* not black kingside */ + assert(pos.castle & (1 << 3)); /* black queenside */ +} + +static void test_parse_fen_invalid_missing_space(void) +{ + Position pos; + int ok = parse_fen(&pos, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"); + assert(!ok); +} + +static void test_parse_fen_invalid_side(void) +{ + Position pos; + int ok = parse_fen(&pos, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR x KQkq - 0 1"); + assert(!ok); +} + +static void test_parse_fen_invalid_ep(void) +{ + Position pos; + int ok = parse_fen(&pos, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq z9 0 1"); + assert(!ok); +} + +static void test_parse_fen_invalid_castling_char(void) +{ + Position pos; + int ok = parse_fen(&pos, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KX - 0 1"); + assert(!ok); +} + +static void test_parse_fen_invalid_off_board(void) +{ + /* Placing a piece on an off-board square (like rank 0 with EMPTY in between) + * Use a FEN where the piece placement lands on off-board sq: try a fen where + * piece placement skips the separator and goes past rank end. Since the parser + * only checks on_board when placing a piece character (not digits), we verify + * that placing AT an off-board square returns 0. This is tricky to trigger via + * a simple digit overflow, but we can use a malformed FEN where the piece + * is placed at sq 0x08 which is off-board: one rank only with 10 pieces. */ + /* After A8-H8 (8 squares) sq=0x78 which is off-board. Place a 9th PIECE. */ + Position pos; + int ok = parse_fen(&pos, "RNBQKBNRP/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1"); + assert(!ok); +} + +static void test_parse_fen_fullmove_and_halfmove(void) +{ + Position pos; + int ok = parse_fen(&pos, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 5 12"); + assert(ok); + assert(pos.halfmove_clock == 5); + assert(pos.fullmove_number == 12); +} + +static void test_set_startpos(void) +{ + Position pos; + set_startpos(&pos); + assert(pos.side == WHITE); + assert(pos.board[0x04] == WK); + assert(pos.board[0x74] == BK); + assert(pos.castle == 0xF); +} + +/* ========================================================================= + * square_from_algebraic tests + * ========================================================================= */ + +static void test_square_from_algebraic_basic(void) +{ + /* e2e4 -> from=e2=0x14, to=e4=0x34 */ + assert(square_from_algebraic("e2e4", 1) == 0x14); + assert(square_from_algebraic("e2e4", 0) == 0x34); +} + +static void test_square_from_algebraic_promotion(void) +{ + /* e7e8q: from e7=0x64, to e8=0x74 */ + assert(square_from_algebraic("e7e8q", 1) == 0x64); + assert(square_from_algebraic("e7e8q", 0) == 0x74); +} + +static void test_square_from_algebraic_null(void) +{ + assert(square_from_algebraic(NULL, 0) == -1); + assert(square_from_algebraic("e2", 0) == -1); /* too short */ +} + +static void test_square_from_algebraic_a1(void) +{ + assert(square_from_algebraic("a1b2", 1) == 0x00); + assert(square_from_algebraic("a1b2", 0) == 0x11); +} + +static void test_square_from_algebraic_h8(void) +{ + assert(square_from_algebraic("h8g7", 1) == 0x77); + assert(square_from_algebraic("h8g7", 0) == 0x66); +} + +/* ========================================================================= + * make_move / unmake_move tests + * ========================================================================= */ + +static void test_make_unmake_pawn_push(void) +{ + Position pos; + set_startpos(&pos); + + /* e2e4 */ + Move m; + int found = move_from_uci(&pos, "e2e4", &m); + assert(found); + + Position before = pos; + Piece cap = EMPTY; + make_move(&pos, &m, &cap); + + assert(cap == EMPTY); + assert(pos.board[0x14] == EMPTY); + assert(pos.board[0x34] == WP); + assert(pos.ep_square == 0x24); /* e3 */ + assert(pos.side == BLACK); + + unmake_move(&pos, &m, cap); + (void)before; + assert(pos.board[0x14] == WP); + assert(pos.board[0x34] == EMPTY); + assert(pos.side == WHITE); +} + +static void test_make_unmake_capture(void) +{ + Position pos; + /* Rxd5: white rook on a5 takes black queen on d5 */ + parse_fen(&pos, "8/8/8/R2q4/8/8/8/K6k w - - 0 1"); + + Move m; + int found = move_from_uci(&pos, "a5d5", &m); + assert(found); + assert(m.is_capture); + + Piece cap = EMPTY; + make_move(&pos, &m, &cap); + assert(cap == BQ); + assert(pos.board[0x40] == EMPTY); /* a5 empty */ + assert(pos.board[0x43] == WR); /* d5 has rook */ + + unmake_move(&pos, &m, cap); + assert(pos.board[0x40] == WR); + assert(pos.board[0x43] == BQ); +} + +static void test_make_unmake_en_passant(void) +{ + Position pos; + /* Black pawn on e4, white just played d2d4, ep on d3 */ + parse_fen(&pos, "8/8/8/8/3Pp3/8/8/4K2k b - d3 0 1"); + + Move m; + int found = move_from_uci(&pos, "e4d3", &m); + assert(found); + assert(m.is_enpassant); + + Piece cap = EMPTY; + make_move(&pos, &m, &cap); + /* White pawn at d4=0x33 should be captured */ + assert(pos.board[0x33] == EMPTY); + /* Black pawn moves from e4=0x34 to d3=0x23 */ + assert(pos.board[0x34] == EMPTY); + assert(pos.board[0x23] == BP); + + unmake_move(&pos, &m, cap); + assert(pos.board[0x33] == WP); /* restored white pawn */ + assert(pos.board[0x34] == BP); /* black pawn back */ + assert(pos.board[0x23] == EMPTY); +} + +static void test_make_unmake_white_kingside_castle(void) +{ + Position pos; + parse_fen(&pos, "4k3/8/8/8/8/8/8/4K2R w K - 0 1"); + + Move m; + int found = move_from_uci(&pos, "e1g1", &m); + assert(found); + assert(m.is_castle); + + Piece cap = EMPTY; + make_move(&pos, &m, &cap); + assert(pos.board[0x06] == WK); + assert(pos.board[0x05] == WR); + assert(pos.board[0x07] == EMPTY); + assert(!(pos.castle & (1 << 0))); /* white kingside right gone */ + + unmake_move(&pos, &m, cap); + assert(pos.board[0x04] == WK); + assert(pos.board[0x07] == WR); + assert(pos.board[0x05] == EMPTY); + assert(pos.board[0x06] == EMPTY); +} + +static void test_make_unmake_white_queenside_castle(void) +{ + Position pos; + parse_fen(&pos, "4k3/8/8/8/8/8/8/R3K3 w Q - 0 1"); + + Move m; + int found = move_from_uci(&pos, "e1c1", &m); + assert(found); + assert(m.is_castle); + + Piece cap = EMPTY; + make_move(&pos, &m, &cap); + assert(pos.board[0x02] == WK); + assert(pos.board[0x03] == WR); + assert(pos.board[0x00] == EMPTY); + + unmake_move(&pos, &m, cap); + assert(pos.board[0x04] == WK); + assert(pos.board[0x00] == WR); +} + +static void test_make_unmake_black_kingside_castle(void) +{ + Position pos; + parse_fen(&pos, "4k2r/8/8/8/8/8/8/4K3 b k - 0 1"); + + Move m; + int found = move_from_uci(&pos, "e8g8", &m); + assert(found); + assert(m.is_castle); + + Piece cap = EMPTY; + make_move(&pos, &m, &cap); + assert(pos.board[0x76] == BK); + assert(pos.board[0x75] == BR); + assert(pos.board[0x77] == EMPTY); + + unmake_move(&pos, &m, cap); + assert(pos.board[0x74] == BK); + assert(pos.board[0x77] == BR); +} + +static void test_make_unmake_black_queenside_castle(void) +{ + Position pos; + parse_fen(&pos, "r3k3/8/8/8/8/8/8/4K3 b q - 0 1"); + + Move m; + int found = move_from_uci(&pos, "e8c8", &m); + assert(found); + assert(m.is_castle); + + Piece cap = EMPTY; + make_move(&pos, &m, &cap); + assert(pos.board[0x72] == BK); + assert(pos.board[0x73] == BR); + assert(pos.board[0x70] == EMPTY); + + unmake_move(&pos, &m, cap); + assert(pos.board[0x74] == BK); + assert(pos.board[0x70] == BR); +} + +static void test_make_promotion_queen(void) +{ + Position pos; + parse_fen(&pos, "7k/4P3/8/8/8/8/8/7K w - - 0 1"); + + Move m; + int found = move_from_uci(&pos, "e7e8q", &m); + assert(found); + assert(m.promo == WQ); + + Piece cap = EMPTY; + make_move(&pos, &m, &cap); + assert(pos.board[0x74] == WQ); + assert(pos.board[0x64] == EMPTY); + + unmake_move(&pos, &m, cap); + /* After unmake: from square should have the pawn (unmake restores moved piece via side) */ + /* unmake sets side back to white first, then assigns moved piece (WP) from promo branch */ + assert(pos.board[0x64] == WP); + assert(pos.board[0x74] == EMPTY); +} + +static void test_make_promotion_rook(void) +{ + Position pos; + parse_fen(&pos, "7k/4P3/8/8/8/8/8/7K w - - 0 1"); + + Move m; + int found = move_from_uci(&pos, "e7e8r", &m); + assert(found); + assert(m.promo == WR); + + Piece cap = EMPTY; + make_move(&pos, &m, &cap); + assert(pos.board[0x74] == WR); +} + +static void test_make_promotion_bishop(void) +{ + Position pos; + parse_fen(&pos, "7k/4P3/8/8/8/8/8/7K w - - 0 1"); + + Move m; + int found = move_from_uci(&pos, "e7e8b", &m); + assert(found); + assert(m.promo == WB); + + Piece cap = EMPTY; + make_move(&pos, &m, &cap); + assert(pos.board[0x74] == WB); +} + +static void test_make_promotion_knight(void) +{ + Position pos; + parse_fen(&pos, "7k/4P3/8/8/8/8/8/7K w - - 0 1"); + + Move m; + int found = move_from_uci(&pos, "e7e8n", &m); + assert(found); + assert(m.promo == WN); + + Piece cap = EMPTY; + make_move(&pos, &m, &cap); + assert(pos.board[0x74] == WN); +} + +static void test_make_castling_rights_update_on_rook_move(void) +{ + Position pos; + parse_fen(&pos, "4k3/8/8/8/8/8/8/R3K2R w KQ - 0 1"); + + /* Move h1 rook */ + Move m; + int found = move_from_uci(&pos, "h1g1", &m); + assert(found); + + Piece cap = EMPTY; + make_move(&pos, &m, &cap); + /* h1 is 0x07; moving from there removes kingside right (bit 0) */ + assert(!(pos.castle & (1 << 0))); + assert(pos.castle & (1 << 1)); /* queenside still intact */ +} + +static void test_make_castling_rights_removed_on_a1_capture(void) +{ + Position pos; + /* Black rook captures white a1 rook */ + parse_fen(&pos, "4k3/8/8/8/8/8/8/R3K3 b Q - 0 1"); + + /* Put a black rook on a2 to capture a1 */ + pos.board[0x10] = BR; + Move m; + int found = move_from_uci(&pos, "a2a1", &m); + assert(found); + + Piece cap = EMPTY; + make_move(&pos, &m, &cap); + assert(!(pos.castle & (1 << 1))); /* white queenside gone */ +} + +/* ========================================================================= + * in_check / gen_moves tests + * ========================================================================= */ + +static void test_in_check_not_in_check(void) +{ + Position pos; + set_startpos(&pos); + assert(!in_check(&pos, WHITE)); + assert(!in_check(&pos, BLACK)); +} + +static void test_in_check_by_rook(void) +{ + Position pos; + /* White king on e1, black rook on e8 - rays clear */ + parse_fen(&pos, "4r3/8/8/8/8/8/8/4K3 w - - 0 1"); + assert(in_check(&pos, WHITE)); + assert(!in_check(&pos, BLACK)); +} + +static void test_in_check_no_king(void) +{ + /* No WK on board - in_check should return 0 gracefully */ + Position pos; + memset(&pos, 0, sizeof(pos)); + for (int i = 0; i < 128; i++) + { + pos.board[i] = EMPTY; + } + pos.ep_square = -1; + assert(!in_check(&pos, WHITE)); +} + +static void test_in_check_by_knight(void) +{ + Position pos; + /* White king on e1 (0x04), black knight on d3 (0x23) attacks e1 */ + parse_fen(&pos, "8/8/8/8/8/3n4/8/4K3 w - - 0 1"); + assert(in_check(&pos, WHITE)); +} + +static void test_in_check_by_bishop(void) +{ + Position pos; + parse_fen(&pos, "8/8/8/8/8/8/6b1/7K w - - 0 1"); + /* Black bishop on g2, white king on h1 - diagonal */ + assert(in_check(&pos, WHITE)); +} + +static void test_in_check_by_pawn(void) +{ + Position pos; + /* White king on e4, black pawn on d5 attacks e4 */ + parse_fen(&pos, "4k3/8/8/3p4/4K3/8/8/8 w - - 0 1"); + assert(in_check(&pos, WHITE)); +} + +static void test_gen_moves_startpos_count(void) +{ + Position pos; + set_startpos(&pos); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 0); + assert(n == 20); /* 16 pawn + 4 knight = 20 */ +} + +static void test_gen_moves_captures_only(void) +{ + Position pos; + set_startpos(&pos); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 1); /* captures only */ + assert(n == 0); /* no captures from start */ +} + +static void test_gen_moves_only_captures_mid_game(void) +{ + Position pos; + /* White rook on d5 can capture black pawn on f5 */ + parse_fen(&pos, "4k3/8/8/3R1p2/8/8/8/4K3 w - - 0 1"); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 1); + assert(n >= 1); +} + +static void test_gen_moves_checkmate(void) +{ + Position pos; + /* Fool's mate: white checkmated */ + parse_fen(&pos, "rnb1kbnr/pppp1ppp/8/4p3/6Pq/5P2/PPPPP2P/RNBQKBNR w KQkq - 1 3"); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 0); + assert(n == 0); /* no legal moves = checkmate */ +} + +static void test_gen_moves_stalemate(void) +{ + Position pos; + /* Classic stalemate: black king on a8, white queen on c7, white king on c8 */ + parse_fen(&pos, "k7/2Q5/2K5/8/8/8/8/8 b - - 0 1"); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 0); + assert(n == 0); + assert(!in_check(&pos, BLACK)); /* stalemate not checkmate */ +} + +static void test_gen_moves_promotes_four_choices(void) +{ + Position pos; + /* White pawn on e7, kings off the e-file so e8 is clear */ + parse_fen(&pos, "7k/4P3/8/8/8/8/8/7K w - - 0 1"); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 0); + int from = 0x64; /* e7 */ + int to = 0x74; /* e8 */ + int cnt = 0; + for (int i = 0; i < n; i++) + { + if (moves[i].from == from && moves[i].to == to) + { + cnt++; + } + } + assert(cnt == 4); /* Q, R, B, N promotions */ +} + +static void test_gen_moves_en_passant(void) +{ + Position pos; + parse_fen(&pos, "4k3/8/8/3Pp3/8/8/8/4K3 w - e6 0 1"); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 0); + /* d5 pawn can capture e.p. to e6=0x54 */ + int found_ep = 0; + for (int i = 0; i < n; i++) + { + if (moves[i].is_enpassant) + { + found_ep = 1; + } + } + assert(found_ep); +} + +static void test_gen_pseudo_not_filtered(void) +{ + Position pos; + /* Self-check position: white king on e1, white rook on e2 pinned by black rook on e8. + * gen_moves_pseudo should include rook moves; gen_moves should filter them. */ + parse_fen(&pos, "4r3/8/8/8/8/8/4R3/4K3 w - - 0 1"); + Move pseudo[256]; + Move legal[256]; + int np = gen_moves_pseudo(&pos, pseudo, 256, 0); + int nl = gen_moves(&pos, legal, 256, 0); + /* Pseudo includes pinned rook moves; legal filters them */ + assert(np > nl); +} + +static void test_gen_moves_knight_attacks(void) +{ + Position pos; + /* Knight on c3, find all squares it attacks */ + parse_fen(&pos, "4k3/8/8/8/8/2N5/8/4K3 w - - 0 1"); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 0); + /* Knight on c3 (0x22) attacks a2,b1,d1,e2,e4,d5,b5,a4 = 8 squares + * Plus king moves */ + int knight_moves = 0; + for (int i = 0; i < n; i++) + { + if (moves[i].from == 0x22) + { + knight_moves++; + } + } + assert(knight_moves == 8); +} + +static void test_gen_moves_bishop_attacks(void) +{ + Position pos; + /* Bishop on d4, open board */ + parse_fen(&pos, "4k3/8/8/8/3B4/8/8/4K3 w - - 0 1"); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 0); + int bishop_moves = 0; + for (int i = 0; i < n; i++) + { + if (moves[i].from == 0x33) + { + bishop_moves++; + } + } + assert(bishop_moves == 13); /* d4 bishop has 13 diagonal squares */ +} + +static void test_gen_moves_rook_attacks(void) +{ + Position pos; + parse_fen(&pos, "4k3/8/8/8/3R4/8/8/4K3 w - - 0 1"); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 0); + int rook_moves = 0; + for (int i = 0; i < n; i++) + { + if (moves[i].from == 0x33) + { + rook_moves++; + } + } + assert(rook_moves == 14); /* d4 rook: 7 horizontal + 7 vertical = 14 */ +} + +static void test_gen_moves_queen_attacks(void) +{ + Position pos; + parse_fen(&pos, "4k3/8/8/8/3Q4/8/8/4K3 w - - 0 1"); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 0); + int queen_moves = 0; + for (int i = 0; i < n; i++) + { + if (moves[i].from == 0x33) + { + queen_moves++; + } + } + assert(queen_moves == 27); /* d4 queen: 13 diagonal + 14 rook = 27 */ +} + +static void test_gen_moves_castling_blocked_by_attack(void) +{ + Position pos; + /* White kingside castle: f1 attacked by black rook - castle should be illegal */ + parse_fen(&pos, "4k2r/8/8/8/8/8/8/4K2R w K - 0 1"); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 0); + int castle_kg = 0; + for (int i = 0; i < n; i++) + { + if (moves[i].is_castle && moves[i].to == 0x06) + { + castle_kg = 1; + } + } + /* Black rook on h8 covers g1, but not f1 - castle IS legal. + * Let's replace with rook on f8 attacking f1. */ + (void)n; + (void)castle_kg; + parse_fen(&pos, "4k1r1/8/8/8/8/8/8/4K2R w K - 0 1"); + n = gen_moves(&pos, moves, 256, 0); + castle_kg = 0; + for (int i = 0; i < n; i++) + { + if (moves[i].is_castle && moves[i].to == 0x06) + { + castle_kg = 1; + } + } + assert(!castle_kg); /* castling blocked by attack on f1 */ +} + +static void test_gen_moves_castling_blocked_in_check(void) +{ + Position pos; + /* White king in check, cannot castle */ + parse_fen(&pos, "4k3/8/8/8/8/8/8/4K2R w K - 0 1"); + /* Put black rook on e8 to give check on e1 */ + pos.board[0x74] = EMPTY; + pos.board[0x74] = BK; + pos.board[0x44] = BR; /* e5 */ + pos.board[0x04] = WK; + + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 0); + int castle_kg = 0; + for (int i = 0; i < n; i++) + { + if (moves[i].is_castle) + { + castle_kg = 1; + } + } + /* Cannot castle while in check */ + assert(!castle_kg); +} + +static void test_gen_moves_black_castling(void) +{ + Position pos; + parse_fen(&pos, "r3k2r/8/8/8/8/8/8/R3K2R b KQkq - 0 1"); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 0); + int qs_castle = 0; + int ks_castle = 0; + for (int i = 0; i < n; i++) + { + if (moves[i].is_castle) + { + if (moves[i].to == 0x72) + { + qs_castle = 1; + } + if (moves[i].to == 0x76) + { + ks_castle = 1; + } + } + } + assert(qs_castle); + assert(ks_castle); +} + +static void test_gen_moves_black_pawn_attacks(void) +{ + Position pos; + /* Black pawn on e5, white pawn on d4 and f4 - both capturable */ + parse_fen(&pos, "4k3/8/8/4p3/3P1P2/8/8/4K3 b - - 0 1"); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 0); + /* e5 pawn can go to e4 (push), d4 (capture), f4 (capture) */ + int from_e5 = 0; + for (int i = 0; i < n; i++) + { + if (moves[i].from == 0x44) + { + from_e5++; + } + } + assert(from_e5 == 3); +} + +static void test_gen_moves_black_pawn_initial_push(void) +{ + Position pos; + parse_fen(&pos, "4k3/4p3/8/8/8/8/8/4K3 b - - 0 1"); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 0); + /* e7 pawn: push to e6 and e5 (double push) */ + int from_e7 = 0; + for (int i = 0; i < n; i++) + { + if (moves[i].from == 0x64) + { + from_e7++; + } + } + assert(from_e7 == 2); +} + +static void test_gen_moves_black_promotion(void) +{ + Position pos; + parse_fen(&pos, "7K/8/8/8/8/8/4p3/7k b - - 0 1"); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 0); + int cnt = 0; + for (int i = 0; i < n; i++) + { + if (moves[i].from == 0x14 && moves[i].to == 0x04) + { + cnt++; + } + } + assert(cnt == 4); /* four promotion choices */ +} + +static void test_gen_moves_black_ep(void) +{ + Position pos; + /* Black pawn on d4, white pawn just moved e2e4, ep square on e3=0x24 */ + parse_fen(&pos, "4k3/8/8/8/3pP3/8/8/4K3 b - e3 0 1"); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 0); + int has_ep = 0; + for (int i = 0; i < n; i++) + { + if (moves[i].is_enpassant) + { + has_ep = 1; + } + } + assert(has_ep); +} + +/* ========================================================================= + * move_from_uci tests + * ========================================================================= */ + +static void test_move_from_uci_basic(void) +{ + Position pos; + set_startpos(&pos); + Move m; + + int ok = move_from_uci(&pos, "e2e4", &m); + assert(ok); + assert(m.from == 0x14); + assert(m.to == 0x34); + assert(!m.promo); +} + +static void test_move_from_uci_invalid_from(void) +{ + Position pos; + set_startpos(&pos); + Move m; + int ok = move_from_uci(&pos, "i9i9", &m); + assert(!ok); +} + +static void test_move_from_uci_not_in_legal_moves(void) +{ + Position pos; + set_startpos(&pos); + Move m; + /* e2e5 is not a legal move */ + int ok = move_from_uci(&pos, "e2e5", &m); + assert(!ok); +} + +static void test_move_from_uci_promotion_queen(void) +{ + Position pos; + parse_fen(&pos, "7k/4P3/8/8/8/8/8/7K w - - 0 1"); + Move m; + int ok = move_from_uci(&pos, "e7e8q", &m); + assert(ok); + assert(m.promo == WQ); +} + +static void test_move_from_uci_promotion_rook(void) +{ + Position pos; + parse_fen(&pos, "7k/4P3/8/8/8/8/8/7K w - - 0 1"); + Move m; + int ok = move_from_uci(&pos, "e7e8r", &m); + assert(ok); + assert(m.promo == WR); +} + +static void test_move_from_uci_promotion_bishop(void) +{ + Position pos; + parse_fen(&pos, "7k/4P3/8/8/8/8/8/7K w - - 0 1"); + Move m; + int ok = move_from_uci(&pos, "e7e8b", &m); + assert(ok); + assert(m.promo == WB); +} + +static void test_move_from_uci_promotion_knight(void) +{ + Position pos; + parse_fen(&pos, "7k/4P3/8/8/8/8/8/7K w - - 0 1"); + Move m; + int ok = move_from_uci(&pos, "e7e8n", &m); + assert(ok); + assert(m.promo == WN); +} + +static void test_move_from_uci_promotion_uppercase_r(void) +{ + Position pos; + parse_fen(&pos, "7k/4P3/8/8/8/8/8/7K w - - 0 1"); + Move m; + int ok = move_from_uci(&pos, "e7e8R", &m); + assert(ok); + assert(m.promo == WR); +} + +static void test_move_from_uci_black_promotion(void) +{ + Position pos; + parse_fen(&pos, "7K/8/8/8/8/8/4p3/7k b - - 0 1"); + Move m; + int ok = move_from_uci(&pos, "e2e1q", &m); + assert(ok); + assert(m.promo == BQ); +} + +/* ========================================================================= + * Perft correctness tests (verifies move generation end-to-end) + * ========================================================================= */ + +static unsigned long long perft(Position pos, int depth) +{ + if (depth == 0) + { + return 1ULL; + } + Move moves[256]; + unsigned long long nodes = 0ULL; + int n = gen_moves(&pos, moves, 256, 0); + for (int i = 0; i < n; i++) + { + Position child = pos; + Piece cap = EMPTY; + make_move(&child, &moves[i], &cap); + nodes += perft(child, depth - 1); + } + return nodes; +} + +static void test_perft_startpos_depth1(void) +{ + Position pos; + set_startpos(&pos); + assert(perft(pos, 1) == 20ULL); +} + +static void test_perft_startpos_depth2(void) +{ + Position pos; + set_startpos(&pos); + assert(perft(pos, 2) == 400ULL); +} + +static void test_perft_ep_position_depth1(void) +{ + Position pos; + /* EP position: black has 22 legal moves at depth 1 */ + parse_fen(&pos, "rnbqkbnr/pppppppp/8/8/3Pp3/8/PPP1PPPP/RNBQKBNR b KQkq d3 0 1"); + assert(perft(pos, 1) == 22ULL); +} + +static void test_perft_kiwipete_depth1(void) +{ + Position pos; + /* Custom position with en passant, castling, and complex moves */ + parse_fen(&pos, "r3k2r/p1ppqpb1/bn2pnp1/2PpP3/1p2P3/2N2N2/PBPP1PPP/R2Q1RK1 w kq - 0 1"); + assert(perft(pos, 1) == 31ULL); +} + +static void test_perft_kiwipete_depth2(void) +{ + Position pos; + parse_fen(&pos, "r3k2r/p1ppqpb1/bn2pnp1/2PpP3/1p2P3/2N2N2/PBPP1PPP/R2Q1RK1 w kq - 0 1"); + assert(perft(pos, 2) == 1315ULL); +} + +/* ========================================================================= + * Additional coverage tests for specific branches + * ========================================================================= */ + +static void test_attacked_by_white_king_proximity(void) +{ + Position pos; + /* Black king on e8 next to white king on e7 - attacked by king */ + parse_fen(&pos, "4k3/4K3/8/8/8/8/8/8 b - - 0 1"); + assert(in_check(&pos, BLACK)); +} + +static void test_attacked_by_white_pawn(void) +{ + Position pos; + /* Black king on f6, white pawn on e5 - pawn attacks f6 */ + parse_fen(&pos, "8/8/5k2/4P3/8/8/8/4K3 b - - 0 1"); + assert(in_check(&pos, BLACK)); +} + +static void test_attacked_by_black_pawn(void) +{ + Position pos; + /* White king on d4, black pawn on e5 - attacks d4? No: e5 pawn attacks d4 and f4 */ + parse_fen(&pos, "4k3/8/8/4p3/3K4/8/8/8 w - - 0 1"); + assert(in_check(&pos, WHITE)); +} + +static void test_attacked_by_queen_rook_direction(void) +{ + Position pos; + /* White king on a1, black queen on a8 - file attack */ + parse_fen(&pos, "q7/8/8/8/8/8/8/K6k w - - 0 1"); + assert(in_check(&pos, WHITE)); +} + +static void test_attacked_slider_blocked(void) +{ + Position pos; + /* White king on a1, black rook on a8, white pawn on a4 blocking */ + parse_fen(&pos, "r7/8/8/8/P7/8/8/K6k w - - 0 1"); + assert(!in_check(&pos, WHITE)); +} + +static void test_halfmove_clock_pawn_resets(void) +{ + Position pos; + set_startpos(&pos); + pos.halfmove_clock = 10; + Move m; + move_from_uci(&pos, "e2e4", &m); + Piece cap = EMPTY; + make_move(&pos, &m, &cap); + assert(pos.halfmove_clock == 0); +} + +static void test_halfmove_clock_piece_increments(void) +{ + Position pos; + parse_fen(&pos, "4k3/8/8/8/8/8/8/4K2R w K - 0 1"); + pos.halfmove_clock = 5; + Move m; + move_from_uci(&pos, "h1g1", &m); + Piece cap = EMPTY; + make_move(&pos, &m, &cap); + assert(pos.halfmove_clock == 6); +} + +static void test_fullmove_increments_after_black(void) +{ + Position pos; + set_startpos(&pos); + int prev_full = pos.fullmove_number; + /* White move first */ + Move m; + move_from_uci(&pos, "e2e4", &m); + Piece cap = EMPTY; + make_move(&pos, &m, &cap); + assert(pos.fullmove_number == prev_full); /* no increment after white */ + /* Now black plays */ + move_from_uci(&pos, "e7e5", &m); + make_move(&pos, &m, &cap); + assert(pos.fullmove_number == prev_full + 1); /* increment after black */ +} + +static void test_move_from_uci_no_promo_mismatch(void) +{ + /* Move has promotion piece but caller provides no promo char - should fail */ + Position pos; + parse_fen(&pos, "7k/4P3/8/8/8/8/8/7K w - - 0 1"); + Move m; + /* "e7e8" without promo char - should not match any promotion */ + int ok = move_from_uci(&pos, "e7e8", &m); + assert(!ok); +} + +static void test_gen_moves_white_pawn_blocked(void) +{ + Position pos; + /* White pawn on e4, black pawn on e5 - blocked, single push only via e3e4 not possible here; + * white pawn on e4 should not be able to push to e5 */ + parse_fen(&pos, "4k3/8/8/4p3/4P3/8/8/4K3 w - - 0 1"); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 0); + int from = 0x34; /* e4 */ + int to = 0x44; /* e5 */ + assert(find_move(moves, n, from, to) == -1); +} + +/* Test make_piece default branch: unknown char treated as EMPTY (covers lines 43-44) */ +static void test_make_piece_unknown_char(void) +{ + Position pos; + /* FEN with 'X' as a piece — make_piece returns EMPTY, parse succeeds */ + int ok = parse_fen(&pos, "X7/8/8/8/8/8/8/k6K w - - 0 1"); + /* Parse succeeds — 'X' treated as empty square */ + assert(ok); + /* a8 (0x70) should be EMPTY since 'X' → EMPTY */ + assert(pos.board[0x70] == EMPTY); +} + +static void test_count_moves_to_uses_helper(void) +{ + Move m[3]; + m[0].to = 0x10; + m[1].to = 0x20; + m[2].to = 0x10; + assert(count_moves_to(m, 3, 0x10) == 2); + assert(count_moves_to(m, 3, 0x20) == 1); + assert(count_moves_to(m, 3, 0x30) == 0); +} + +static void test_in_check_by_queen_diagonal(void) +{ + Position pos; + /* White king on e1, black queen on h4 - diagonal attack */ + parse_fen(&pos, "4k3/8/8/8/7q/8/8/4K3 w - - 0 1"); + assert(in_check(&pos, WHITE)); +} + +static void test_gen_moves_white_queenside_castle_no_attack_on_d1(void) +{ + Position pos; + /* White can queenside castle - verify no illegal pieces on attack */ + parse_fen(&pos, "4k3/8/8/8/8/8/8/R3K3 w Q - 0 1"); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 0); + int qs_present = 0; + for (int i = 0; i < n; i++) + { + if (moves[i].is_castle && moves[i].to == 0x02) + { + qs_present = 1; + } + } + assert(qs_present); +} + +static void test_gen_moves_make_captures_only_with_capture(void) +{ + Position pos; + /* White knight on c3 can capture a black pawn on b5 */ + parse_fen(&pos, "4k3/8/8/1p6/8/2N5/8/4K3 w - - 0 1"); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 1); /* captures_only=1 */ + /* Knight on c3 (0x22) should be able to capture on b5 (0x41) */ + int found = 0; + for (int i = 0; i < n; i++) + { + if (moves[i].from == 0x22 && moves[i].is_capture) + { + found = 1; + } + } + assert(found); +} + +/* Test white knight checking black king (covers line 272: return 1 for WN in square_attacked_by) */ +static void test_in_check_by_white_knight(void) +{ + Position pos; + /* White knight on f3 attacks black king on g5? No, f3+14=g5? 0x25+14=0x33 d4 no. + * Use: white knight on d6 (0x53), black king on e8 (0x74). + * d6+0x1E? Nope, knight offsets: 33,31,18,14,-33,-31,-18,-14 + * 0x53 + 0x21 = 0x74 yes! 33 decimal = 0x21. So WN on d6 attacks e8. */ + parse_fen(&pos, "4k3/8/3N4/8/8/8/8/4K3 b - - 0 1"); + assert(in_check(&pos, BLACK)); +} + +/* Test black king adjacent to white king square (covers line 295 in square_attacked_by for BK) */ +static void test_square_attacked_by_black_king(void) +{ + Position pos; + /* White king on d4 (0x33), black king on e5 (0x44) - they are adjacent. + * in_check(pos, WHITE) checks if white king is attacked by BLACK. + * square_attacked_by(pos, 0x33, BLACK) looks for BK adjacent to 0x33. + * e5 = 0x44, 0x44 - 0x33 = 0x11 = 17 which is in kd[] yes. */ + parse_fen(&pos, "8/8/8/4k3/3K4/8/8/8 w - - 0 1"); + assert(in_check(&pos, WHITE)); /* black king attacks white king */ +} + +/* Test pawn on s1 diagonal (covers line 305: s1 = sq - 15 = wp on g-file attacking from h-file) */ +static void test_white_pawn_attacks_s1_diagonal(void) +{ + Position pos; + /* Black king on h5 (0x47), white pawn on g4 (0x36). + * s1 = sq - 15 (decimal) = 0x47 - 0x0F = 0x38 (a5? No: 0x47-0x0F=0x38 which is... + * 0x38 = rank 3 (3<<4=0x30), file 8 = off-board (0x08 bit set, 0x38&0x88 = 0x08) + * Off board! Let me try black king at f6 (0x55). + * s1 = 0x55 - 15 = 0x55 - 0x0F = 0x46 = g5 */ + /* White pawn on g5 (0x46), black king on f6 (0x55). + * Does g5 WP attack f6? WP attacks sq+15 and sq+17 from OWN pawn perspective. + * But here square_attacked_by checks pos->board[sq-15] == WP, i.e. pos->board[0x46] == WP. YES! + */ + parse_fen(&pos, "8/8/5k2/6P1/8/8/8/4K3 b - - 0 1"); + assert(in_check(&pos, BLACK)); +} + +/* Test queen capture via rook direction (covers line 601) */ +static void test_queen_rook_direction_capture(void) +{ + Position pos; + /* White queen on d4, black rook on d7 - queen captures along d-file */ + parse_fen(&pos, "4k3/3r4/8/8/3Q4/8/8/4K3 w - - 0 1"); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 0); + int found = 0; + for (int i = 0; i < n; i++) + { + /* Queen on d4=0x33 capturing rook on d7=0x63 via rook direction */ + if (moves[i].from == 0x33 && moves[i].to == 0x63 && moves[i].is_capture) + { + found = 1; + } + } + assert(found); +} + +/* Test king capture (covers line 657) */ +static void test_king_capture(void) +{ + Position pos; + /* White king adjacent to black pawn - king captures it */ + parse_fen(&pos, "4k3/8/8/8/8/8/5p2/4K3 w - - 0 1"); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 0); + int found = 0; + for (int i = 0; i < n; i++) + { + if (moves[i].from == 0x04 && moves[i].is_capture) + { + found = 1; + } + } + assert(found); +} + +/* Test capture promotion (pawn on 7th rank captures diagonally to 8th, covers lines 477-483) */ +static void test_white_pawn_capture_promotion(void) +{ + Position pos; + /* White pawn on d7, black rook on e8 - captures and promotes */ + parse_fen(&pos, "4kr2/3P4/8/8/8/8/8/7K w - - 0 1"); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 0); + int from = 0x63; /* d7 */ + int to = 0x74; /* e8 */ + int cnt = 0; + for (int i = 0; i < n; i++) + { + if (moves[i].from == from && moves[i].to == to && moves[i].is_capture) + { + cnt++; + } + } + assert(cnt == 4); /* Q, R, B, N capture-promotions */ +} + +/* Test black pawn capture promotion (covers black path of lines 477-483) */ +static void test_black_pawn_capture_promotion(void) +{ + Position pos; + /* Black pawn on d2, white rook on e1 - captures and promotes */ + parse_fen(&pos, "7k/8/8/8/8/8/3p4/4RK2 b - - 0 1"); + Move moves[256]; + int n = gen_moves(&pos, moves, 256, 0); + int cnt = 0; + for (int i = 0; i < n; i++) + { + if (moves[i].from == 0x13 && moves[i].to == 0x04 && moves[i].is_capture) + { + cnt++; + } + } + assert(cnt == 4); +} + +/* Test add_move overflow (covers line 244: return count when count >= max) */ +static void test_add_move_max_overflow(void) +{ + Position pos; + /* Use gen_moves with max_moves=0 to trigger count >= max */ + set_startpos(&pos); + Move moves[1]; + int n = gen_moves(&pos, moves, 0, 0); /* max_moves=0 */ + assert(n == 0); /* add_move returns count (0) immediately */ +} + +/* Test FEN invalid: no space between side and castling (covers line 153) */ +static void test_parse_fen_missing_space_after_side(void) +{ + Position pos; + /* "8/8/8/8/8/8/8/8 w- - 0 1": missing space between side 'w' and castling '-' */ + int ok = parse_fen(&pos, "8/8/8/8/8/8/8/8 w- - 0 1"); + assert(!ok); +} + +/* Test FEN invalid: no space after castling (covers line 191) */ +static void test_parse_fen_missing_space_after_castling(void) +{ + Position pos; + /* After castling "-" expect space, but give no space */ + int ok = parse_fen(&pos, "8/8/8/8/8/8/8/8 w -- 0 1"); + assert(!ok); +} + +int main(void) +{ + /* parse_fen / set_startpos */ + test_parse_fen_startpos(); + test_parse_fen_black_to_move(); + test_parse_fen_no_castling(); + test_parse_fen_partial_castling(); + test_parse_fen_invalid_missing_space(); + test_parse_fen_invalid_side(); + test_parse_fen_invalid_ep(); + test_parse_fen_invalid_castling_char(); + test_parse_fen_invalid_off_board(); + test_parse_fen_fullmove_and_halfmove(); + test_set_startpos(); + + /* square_from_algebraic */ + test_square_from_algebraic_basic(); + test_square_from_algebraic_promotion(); + test_square_from_algebraic_null(); + test_square_from_algebraic_a1(); + test_square_from_algebraic_h8(); + + /* make_move / unmake_move */ + test_make_unmake_pawn_push(); + test_make_unmake_capture(); + test_make_unmake_en_passant(); + test_make_unmake_white_kingside_castle(); + test_make_unmake_white_queenside_castle(); + test_make_unmake_black_kingside_castle(); + test_make_unmake_black_queenside_castle(); + test_make_promotion_queen(); + test_make_promotion_rook(); + test_make_promotion_bishop(); + test_make_promotion_knight(); + test_make_castling_rights_update_on_rook_move(); + test_make_castling_rights_removed_on_a1_capture(); + + /* in_check */ + test_in_check_not_in_check(); + test_in_check_by_rook(); + test_in_check_no_king(); + test_in_check_by_knight(); + test_in_check_by_bishop(); + test_in_check_by_pawn(); + + /* gen_moves */ + test_gen_moves_startpos_count(); + test_gen_moves_captures_only(); + test_gen_moves_only_captures_mid_game(); + test_gen_moves_checkmate(); + test_gen_moves_stalemate(); + test_gen_moves_promotes_four_choices(); + test_gen_moves_en_passant(); + test_gen_pseudo_not_filtered(); + test_gen_moves_knight_attacks(); + test_gen_moves_bishop_attacks(); + test_gen_moves_rook_attacks(); + test_gen_moves_queen_attacks(); + test_gen_moves_castling_blocked_by_attack(); + test_gen_moves_castling_blocked_in_check(); + test_gen_moves_black_castling(); + test_gen_moves_black_pawn_attacks(); + test_gen_moves_black_pawn_initial_push(); + test_gen_moves_black_promotion(); + test_gen_moves_black_ep(); + + /* move_from_uci */ + test_move_from_uci_basic(); + test_move_from_uci_invalid_from(); + test_move_from_uci_not_in_legal_moves(); + test_move_from_uci_promotion_queen(); + test_move_from_uci_promotion_rook(); + test_move_from_uci_promotion_bishop(); + test_move_from_uci_promotion_knight(); + test_move_from_uci_promotion_uppercase_r(); + test_move_from_uci_black_promotion(); + + /* Perft correctness */ + test_perft_startpos_depth1(); + test_perft_startpos_depth2(); + test_perft_ep_position_depth1(); + test_perft_kiwipete_depth1(); + test_perft_kiwipete_depth2(); + + /* Additional coverage */ + test_attacked_by_white_king_proximity(); + test_attacked_by_white_pawn(); + test_attacked_by_black_pawn(); + test_attacked_by_queen_rook_direction(); + test_attacked_slider_blocked(); + test_halfmove_clock_pawn_resets(); + test_halfmove_clock_piece_increments(); + test_fullmove_increments_after_black(); + test_move_from_uci_no_promo_mismatch(); + test_gen_moves_white_pawn_blocked(); + test_count_moves_to_uses_helper(); + test_in_check_by_queen_diagonal(); + test_gen_moves_white_queenside_castle_no_attack_on_d1(); + test_gen_moves_make_captures_only_with_capture(); + test_in_check_by_white_knight(); + test_square_attacked_by_black_king(); + test_white_pawn_attacks_s1_diagonal(); + test_queen_rook_direction_capture(); + test_king_capture(); + test_white_pawn_capture_promotion(); + test_black_pawn_capture_promotion(); + test_add_move_max_overflow(); + test_parse_fen_missing_space_after_side(); + test_parse_fen_missing_space_after_castling(); + test_make_piece_unknown_char(); + + printf("All tests passed (%d tests).\n", 89); + return 0; +} diff --git a/C/lichess_random_engine/test_search.c b/C/lichess_random_engine/test_search.c new file mode 100644 index 0000000..3be8fbc --- /dev/null +++ b/C/lichess_random_engine/test_search.c @@ -0,0 +1,190 @@ +/* + * test_search.c - Unit tests for search.c (evaluate, alphabeta). + */ + +#include "movegen.h" +#include "search.h" + +#include +#include + +/* ========================================================================= + * evaluate tests + * ========================================================================= */ + +static void test_evaluate_startpos_equal(void) +{ + Position pos; + set_startpos(&pos); + /* Symmetric position: score from white perspective should be 0 */ + assert(evaluate(&pos) == 0); +} + +static void test_evaluate_white_extra_queen(void) +{ + Position pos; + /* White has an extra queen */ + parse_fen(&pos, "4k3/8/8/8/8/8/8/Q3K3 w - - 0 1"); + int score = evaluate(&pos); + assert(score > 0); /* White favored */ +} + +static void test_evaluate_black_extra_queen(void) +{ + Position pos; + /* Black has an extra queen */ + parse_fen(&pos, "4k1q1/8/8/8/8/8/8/4K3 w - - 0 1"); + int score = evaluate(&pos); + assert(score < 0); /* Black favored from white's perspective */ +} + +static void test_evaluate_symmetric_material(void) +{ + Position pos; + /* Equal material: rook vs rook, same side */ + parse_fen(&pos, "4k2r/8/8/8/8/8/8/4K2R w - - 0 1"); + assert(evaluate(&pos) == 0); +} + +static void test_evaluate_pawn_advantage(void) +{ + Position pos; + /* White has 2 extra pawns */ + parse_fen(&pos, "4k3/8/8/8/8/8/1PP5/4K3 w - - 0 1"); + int white_score = evaluate(&pos); + pos.side = BLACK; + int black_score = evaluate(&pos); + assert(white_score > 0); + assert(black_score < 0); /* same position but from black's perspective */ +} + +static void test_evaluate_all_piece_types(void) +{ + Position pos; + /* White: K+Q+R+B+N vs Black: K only */ + parse_fen(&pos, "4k3/8/8/8/8/8/8/QRBN1K2 w - - 0 1"); + int score = evaluate(&pos); + assert(score > 0); + /* White material: Q=900 + R=500 + B=330 + N=320 = 2050 */ + assert(score == 900 + 500 + 330 + 320); +} + +static void test_evaluate_black_pieces(void) +{ + Position pos; + /* Black: K+Q+R+B+N vs White: K only */ + parse_fen(&pos, "4kqrb/4n3/8/8/8/8/8/4K3 w - - 0 1"); + int score = evaluate(&pos); + /* From white perspective, should be heavily negative */ + assert(score < -1000); +} + +/* ========================================================================= + * alphabeta tests + * ========================================================================= */ + +static void test_alphabeta_single_capture(void) +{ + Position pos; + /* White rook can capture black queen - best move immediately obvious at depth 1 */ + parse_fen(&pos, "4k3/8/8/3q4/3R4/8/8/4K3 w - - 0 1"); + PrincipalVariation pv = {.from = -1, .to = -1}; + int score = alphabeta(pos, 1, -30000, 30000, &pv); + assert(score > 0); /* Should find winning position */ + /* Best move should be d4d5 (rook captures queen on d5) */ + assert(pv.from >= 0); + assert(pv.to >= 0); +} + +static void test_alphabeta_checkmate_in_one(void) +{ + Position pos; + /* White queen delivers checkmate at f7 */ + parse_fen(&pos, "r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5Q2/PPPP1PPP/RNB1K1NR w KQkq - 4 4"); + PrincipalVariation pv = {.from = -1, .to = -1}; + int score = alphabeta(pos, 2, -30000, 30000, &pv); + /* Depth 2: should find the mating sequence */ + (void)score; + assert(pv.from >= 0); +} + +static void test_alphabeta_stalemate_score(void) +{ + Position pos; + /* Black king stalemated: ensure score is 0 (stalemate = draw) + * k on a8, white queen on c7, white king on c6 */ + parse_fen(&pos, "k7/2Q5/2K5/8/8/8/8/8 b - - 0 1"); + /* Black to move, stalemated */ + PrincipalVariation pv = {.from = -1, .to = -1}; + int score = alphabeta(pos, 1, -30000, 30000, &pv); + assert(score == 0); /* stalemate */ +} + +static void test_alphabeta_checkmate_score(void) +{ + Position pos; + /* Black king checkmated (fool's mate) */ + parse_fen(&pos, "rnb1kbnr/pppp1ppp/8/4p3/6Pq/5P2/PPPPP2P/RNBQKBNR w KQkq - 1 3"); + /* White is mated; at depth 0 just evaluates material */ + PrincipalVariation pv = {.from = -1, .to = -1}; + int score = alphabeta(pos, 1, -30000, 30000, &pv); + /* White has no legal moves - should return very negative score */ + assert(score < -20000); +} + +static void test_alphabeta_depth_zero(void) +{ + Position pos; + set_startpos(&pos); + PrincipalVariation pv = {.from = -1, .to = -1}; + int score = alphabeta(pos, 0, -30000, 30000, &pv); + /* At depth 0, returns leaf evaluation */ + assert(score == 0); /* startpos is equal */ +} + +static void test_alphabeta_no_pv_null(void) +{ + Position pos; + set_startpos(&pos); + /* Pass NULL for pv - should not crash */ + int score = alphabeta(pos, 1, -30000, 30000, NULL); + (void)score; + /* Just checking no crash */ + assert(1); +} + +static void test_alphabeta_beta_cutoff(void) +{ + Position pos; + /* Need a position where beta cutoff fires: search at depth 2+ */ + set_startpos(&pos); + PrincipalVariation pv = {.from = -1, .to = -1}; + int score = alphabeta(pos, 2, -30000, 30000, &pv); + /* Symmetric start - should be near 0 */ + (void)score; + assert(pv.from >= 0); +} + +int main(void) +{ + /* evaluate */ + test_evaluate_startpos_equal(); + test_evaluate_white_extra_queen(); + test_evaluate_black_extra_queen(); + test_evaluate_symmetric_material(); + test_evaluate_pawn_advantage(); + test_evaluate_all_piece_types(); + test_evaluate_black_pieces(); + + /* alphabeta */ + test_alphabeta_single_capture(); + test_alphabeta_checkmate_in_one(); + test_alphabeta_stalemate_score(); + test_alphabeta_checkmate_score(); + test_alphabeta_depth_zero(); + test_alphabeta_no_pv_null(); + test_alphabeta_beta_cutoff(); + + printf("All tests passed (%d tests).\n", 14); + return 0; +} diff --git a/C/misc/split/.gitignore b/C/misc/split/.gitignore index 075ea8d..e3e5ca3 100644 --- a/C/misc/split/.gitignore +++ b/C/misc/split/.gitignore @@ -1 +1,7 @@ split +test_split +*.gcda +*.gcno +*.gcov +coverage.info +coverage_html/ diff --git a/C/misc/split/Makefile b/C/misc/split/Makefile index 5400fa9..6f138bd 100644 --- a/C/misc/split/Makefile +++ b/C/misc/split/Makefile @@ -2,9 +2,15 @@ CC := gcc CFLAGS := -O2 -Wall -Wextra -std=c11 LDFLAGS := -SRC := main.c +SRC := main.c split.c BIN := split +TEST_SRC := test_split.c split.c +TEST_BIN := test_split + +COV_CFLAGS := -Wall -Wextra -std=c11 --coverage -g -O0 +COV_LDFLAGS := -lm + all: $(BIN) $(BIN): $(SRC) @@ -13,7 +19,29 @@ $(BIN): $(SRC) run: $(BIN) ./$(BIN) -clean: - rm -f $(BIN) +test: $(TEST_BIN) + ./$(TEST_BIN) -.PHONY: all run clean +$(TEST_BIN): $(TEST_SRC) + $(CC) $(CFLAGS) -o $@ $^ -lm + +coverage: + $(CC) $(COV_CFLAGS) -o $(TEST_BIN) $(TEST_SRC) $(COV_LDFLAGS) + ./$(TEST_BIN) + lcov --capture --directory . --output-file coverage.info --rc branch_coverage=1 + lcov --remove coverage.info '/usr/*' --output-file coverage.info \ + --rc branch_coverage=1 --ignore-errors unused + @LINE_PCT=$$(lcov --summary coverage.info 2>&1 | grep -oP 'lines\.*:\s*\K[0-9.]+'); \ + echo "Line coverage: $${LINE_PCT}%"; \ + if [ "$$(echo "$${LINE_PCT} < 100.0" | bc -l)" = "1" ]; then \ + echo "FAIL: Line coverage $${LINE_PCT}% is below 100%"; \ + exit 1; \ + else \ + echo "OK: 100% line coverage achieved"; \ + fi + +clean: + rm -f $(BIN) $(TEST_BIN) *.gcda *.gcno *.gcov coverage.info + rm -rf coverage_html + +.PHONY: all run test coverage clean diff --git a/C/misc/split/main.c b/C/misc/split/main.c index 634651d..0ea57e1 100644 --- a/C/misc/split/main.c +++ b/C/misc/split/main.c @@ -1,86 +1,6 @@ #include -#include -// Function to calculate symmetric weights for both even and odd N -void calculate_symmetric_weights(int N, double middle_weight, const double *factors, - double *weights) -{ - int half_N = N / 2; - int i = 0; - weights[half_N] = middle_weight; // Middle value for symmetry - - // Calculate left side weights - if (factors) - { - for (i = 0; i < half_N; i++) - { - if (i == 0) - { - weights[half_N - i - 1] = middle_weight + factors[i]; - } - else - { - weights[half_N - i - 1] = weights[half_N - i] + factors[i]; - } - } - } - else - { - for (i = 0; i < half_N; i++) - { - weights[half_N - i - 1] = middle_weight - (i + 1); - } - } - - // Mirror left side weights to right side - for (i = 0; i < half_N; i++) - { - weights[half_N + i + 1] = weights[half_N - i - 1]; - } -} - -// Function to scale the weights so that their sum is proportional to X -void scale_to_total(double X, const double *weights, int N, double *distances) -{ - double total_weight = 0; - int i = 0; - - // Calculate the total weight - for (i = 0; i < N; i++) - { - total_weight += weights[i]; - } - - double base_unit = X / total_weight; - - // Scale weights - for (i = 0; i < N; i++) - { - distances[i] = base_unit * weights[i]; - } -} - -// Function to split X into N parts symmetrically -void split_x_into_n_symmetrically(double X, int N, double *factors, double *distances) -{ - double *weights = (double *)malloc(N * sizeof(double)); - - calculate_symmetric_weights(N, 1.0, factors, weights); - scale_to_total(X, weights, N, distances); - - free(weights); -} - -// Function to split X into N parts, with a specific middle value -void split_x_into_n_middle(double X, int N, double middle_value, double *distances) -{ - double *weights = (double *)malloc(N * sizeof(double)); - - calculate_symmetric_weights(N, middle_value, NULL, weights); - scale_to_total(X, weights, N, distances); - - free(weights); -} +#include "split.h" // Example usage int main(void) diff --git a/C/misc/split/split.c b/C/misc/split/split.c new file mode 100644 index 0000000..ada4a2e --- /dev/null +++ b/C/misc/split/split.c @@ -0,0 +1,80 @@ +#include + +#include "split.h" + +void calculate_symmetric_weights(int N, double middle_weight, const double *factors, + double *weights) +{ + int half_N = N / 2; + int i = 0; + weights[half_N] = middle_weight; + + if (factors) + { + for (i = 0; i < half_N; i++) + { + if (i == 0) + { + weights[half_N - i - 1] = middle_weight + factors[i]; + } + else + { + weights[half_N - i - 1] = weights[half_N - i] + factors[i]; + } + } + } + else + { + for (i = 0; i < half_N; i++) + { + weights[half_N - i - 1] = middle_weight - (i + 1); + } + } + + for (i = 0; i < half_N; i++) + { + weights[half_N + i + 1] = weights[half_N - i - 1]; + } +} + +void scale_to_total(double X, const double *weights, int N, double *distances) +{ + double total_weight = 0; + int i = 0; + + for (i = 0; i < N; i++) + { + total_weight += weights[i]; + } + + double base_unit = X / total_weight; + + for (i = 0; i < N; i++) + { + distances[i] = base_unit * weights[i]; + } +} + +void split_x_into_n_symmetrically(double X, int N, double *factors, double *distances) +{ + double *weights = (double *)malloc((size_t)N * sizeof(double)); + if (!weights) + return; + + calculate_symmetric_weights(N, 1.0, factors, weights); + scale_to_total(X, weights, N, distances); + + free(weights); +} + +void split_x_into_n_middle(double X, int N, double middle_value, double *distances) +{ + double *weights = (double *)malloc((size_t)N * sizeof(double)); + if (!weights) + return; + + calculate_symmetric_weights(N, middle_value, NULL, weights); + scale_to_total(X, weights, N, distances); + + free(weights); +} diff --git a/C/misc/split/split.h b/C/misc/split/split.h new file mode 100644 index 0000000..e6c086f --- /dev/null +++ b/C/misc/split/split.h @@ -0,0 +1,13 @@ +#ifndef SPLIT_H +#define SPLIT_H + +void calculate_symmetric_weights(int N, double middle_weight, const double *factors, + double *weights); + +void scale_to_total(double X, const double *weights, int N, double *distances); + +void split_x_into_n_symmetrically(double X, int N, double *factors, double *distances); + +void split_x_into_n_middle(double X, int N, double middle_value, double *distances); + +#endif diff --git a/C/misc/split/test_split.c b/C/misc/split/test_split.c new file mode 100644 index 0000000..e879505 --- /dev/null +++ b/C/misc/split/test_split.c @@ -0,0 +1,214 @@ +#include +#include +#include + +#include "split.h" + +#define EPSILON 1e-9 + +static void assert_close(double a, double b) { assert(fabs(a - b) < EPSILON); } + +static double sum_array(const double *arr, int n) +{ + double s = 0; + for (int i = 0; i < n; i++) + { + s += arr[i]; + } + return s; +} + +/* calculate_symmetric_weights: with factors, odd N */ +static void test_symmetric_weights_with_factors_odd(void) +{ + double weights[5]; + double factors[2] = {1.0, 2.0}; + + calculate_symmetric_weights(5, 1.0, factors, weights); + + /* middle = 1.0 */ + assert_close(weights[2], 1.0); + /* i=0: weights[1] = middle + factors[0] = 2.0 */ + assert_close(weights[1], 2.0); + /* i=1: weights[0] = weights[1] + factors[1] = 4.0 */ + assert_close(weights[0], 4.0); + /* mirror: weights[3] = weights[1] = 2.0, weights[4] = weights[0] = 4.0 */ + assert_close(weights[3], 2.0); + assert_close(weights[4], 4.0); +} + +/* calculate_symmetric_weights: with factors, even N */ +static void test_symmetric_weights_with_factors_even(void) +{ + double weights[4]; + double factors[2] = {0.5, 1.5}; + + calculate_symmetric_weights(4, 3.0, factors, weights); + + /* half_N = 2, middle index = 2 */ + assert_close(weights[2], 3.0); + /* i=0: weights[1] = 3.0 + 0.5 = 3.5 */ + assert_close(weights[1], 3.5); + /* i=1: weights[0] = weights[1] + 1.5 = 5.0 */ + assert_close(weights[0], 5.0); + /* mirror: weights[3] = weights[1] = 3.5 */ + assert_close(weights[3], 3.5); +} + +/* calculate_symmetric_weights: without factors (NULL), odd N */ +static void test_symmetric_weights_null_factors_odd(void) +{ + double weights[5]; + + calculate_symmetric_weights(5, 5.0, NULL, weights); + + /* middle = 5.0 */ + assert_close(weights[2], 5.0); + /* i=0: weights[1] = 5.0 - 1 = 4.0 */ + assert_close(weights[1], 4.0); + /* i=1: weights[0] = 5.0 - 2 = 3.0 */ + assert_close(weights[0], 3.0); + /* mirror */ + assert_close(weights[3], 4.0); + assert_close(weights[4], 3.0); +} + +/* calculate_symmetric_weights: without factors, even N */ +static void test_symmetric_weights_null_factors_even(void) +{ + double weights[6]; + + calculate_symmetric_weights(6, 10.0, NULL, weights); + + /* half_N = 3, middle index = 3 */ + assert_close(weights[3], 10.0); + /* i=0: weights[2] = 10.0 - 1 = 9.0 */ + assert_close(weights[2], 9.0); + /* i=1: weights[1] = 10.0 - 2 = 8.0 */ + assert_close(weights[1], 8.0); + /* i=2: weights[0] = 10.0 - 3 = 7.0 */ + assert_close(weights[0], 7.0); + /* mirror */ + assert_close(weights[4], 9.0); + assert_close(weights[5], 8.0); +} + +/* calculate_symmetric_weights: N=1 (half_N=0, loops don't execute) */ +static void test_symmetric_weights_n1(void) +{ + double weights[1]; + + calculate_symmetric_weights(1, 42.0, NULL, weights); + assert_close(weights[0], 42.0); + + double factors[1] = {99.0}; + calculate_symmetric_weights(1, 7.0, factors, weights); + assert_close(weights[0], 7.0); +} + +/* scale_to_total: verify distances sum to X */ +static void test_scale_to_total(void) +{ + double weights[3] = {1.0, 2.0, 1.0}; + double distances[3] = {0}; + + scale_to_total(100.0, weights, 3, distances); + + assert_close(sum_array(distances, 3), 100.0); + /* total_weight = 4, base_unit = 25 */ + assert_close(distances[0], 25.0); + assert_close(distances[1], 50.0); + assert_close(distances[2], 25.0); +} + +/* scale_to_total: single element */ +static void test_scale_to_total_single(void) +{ + double weights[1] = {5.0}; + double distances[1] = {0}; + + scale_to_total(200.0, weights, 1, distances); + assert_close(distances[0], 200.0); +} + +/* split_x_into_n_symmetrically: N=5 with factors */ +static void test_split_symmetrically(void) +{ + double factors[2] = {1.0, 2.0}; + double distances[5] = {0}; + + split_x_into_n_symmetrically(100.0, 5, factors, distances); + + /* weights: [4, 2, 1, 2, 4] => total=13, base_unit=100/13 */ + assert_close(sum_array(distances, 5), 100.0); + /* symmetry */ + assert_close(distances[0], distances[4]); + assert_close(distances[1], distances[3]); + /* middle is smallest */ + assert(distances[2] < distances[1]); + assert(distances[1] < distances[0]); +} + +/* split_x_into_n_symmetrically: N=3 with factors */ +static void test_split_symmetrically_n3(void) +{ + double factors[1] = {2.0}; + double distances[3] = {0}; + + split_x_into_n_symmetrically(60.0, 3, factors, distances); + + assert_close(sum_array(distances, 3), 60.0); + assert_close(distances[0], distances[2]); +} + +/* split_x_into_n_middle: N=5 with middle value */ +static void test_split_middle(void) +{ + double distances[5] = {0}; + + split_x_into_n_middle(100.0, 5, 5.0, distances); + + assert_close(sum_array(distances, 5), 100.0); + /* symmetry */ + assert_close(distances[0], distances[4]); + assert_close(distances[1], distances[3]); +} + +/* split_x_into_n_middle: N=3 with middle value */ +static void test_split_middle_n3(void) +{ + double distances[3] = {0}; + + split_x_into_n_middle(90.0, 3, 10.0, distances); + + assert_close(sum_array(distances, 3), 90.0); + assert_close(distances[0], distances[2]); +} + +/* split_x_into_n_middle: N=1 */ +static void test_split_middle_n1(void) +{ + double distances[1] = {0}; + + split_x_into_n_middle(50.0, 1, 7.0, distances); + assert_close(distances[0], 50.0); +} + +int main(void) +{ + test_symmetric_weights_with_factors_odd(); + test_symmetric_weights_with_factors_even(); + test_symmetric_weights_null_factors_odd(); + test_symmetric_weights_null_factors_even(); + test_symmetric_weights_n1(); + test_scale_to_total(); + test_scale_to_total_single(); + test_split_symmetrically(); + test_split_symmetrically_n3(); + test_split_middle(); + test_split_middle_n3(); + test_split_middle_n1(); + + printf("All tests passed.\n"); + return 0; +} diff --git a/C/vocabulary_curve/Makefile b/C/vocabulary_curve/Makefile index 6311950..3112b7a 100644 --- a/C/vocabulary_curve/Makefile +++ b/C/vocabulary_curve/Makefile @@ -1,13 +1,43 @@ -CC = gcc -CFLAGS = -O3 -Wall -Wextra -march=native -TARGET = vocabulary_curve +CC = gcc +CFLAGS = -O3 -Wall -Wextra -march=native +COV = -O0 -g --coverage -Wall -Wextra +TARGET = vocabulary_curve all: $(TARGET) -$(TARGET): main.c - $(CC) $(CFLAGS) -o $(TARGET) main.c +$(TARGET): main.c vocabulary.c vocabulary.h + $(CC) $(CFLAGS) -o $(TARGET) main.c vocabulary.c + +# ---- tests --------------------------------------------------------------- + +test_vocabulary: test_vocabulary.c vocabulary.c vocabulary.h + $(CC) $(COV) -o test_vocabulary test_vocabulary.c vocabulary.c + +test: test_vocabulary + ./test_vocabulary + +# ---- coverage ------------------------------------------------------------ + +coverage: test_vocabulary + ./test_vocabulary + lcov --capture --directory . --output-file coverage.info \ + --rc branch_coverage=1 --ignore-errors inconsistent,mismatch,unused + lcov --extract coverage.info "$(CURDIR)/vocabulary.c" \ + --output-file coverage.info \ + --ignore-errors unused,inconsistent + @echo "--- Coverage Summary ---" + lcov --summary coverage.info --rc branch_coverage=1 2>&1 | tee /tmp/lcov_summary.txt + @LINE_COV=$$(grep "lines" /tmp/lcov_summary.txt | grep -oP '[0-9]+\.[0-9]+(?=%)'); \ + echo "Line coverage: $${LINE_COV}%"; \ + if [ "$$(echo "$${LINE_COV} < 100.0" | bc)" = "1" ]; then \ + echo "FAIL: line coverage below 100%"; exit 1; \ + fi + @echo "OK: 100% line coverage achieved" + +run: $(TARGET) + ./$(TARGET) clean: - rm -f $(TARGET) + rm -f $(TARGET) test_vocabulary *.gcda *.gcno *.gcov coverage.info -.PHONY: all clean +.PHONY: all run clean test coverage diff --git a/C/vocabulary_curve/main.c b/C/vocabulary_curve/main.c index 68b3327..c266756 100644 --- a/C/vocabulary_curve/main.c +++ b/C/vocabulary_curve/main.c @@ -1,282 +1,36 @@ /* - * Vocabulary Learning Curve Analyzer - * - * For each excerpt length (1, 2, 3, ... N words), finds the excerpt that - * requires the minimum number of top-frequency words to understand 100%. - * - * Usage: - * ./vocabulary_curve [max_length] - * ./vocabulary_curve test.txt 50 + * Vocabulary Learning Curve Analyzer - thin driver. */ -#include +#include "vocabulary.h" + #include #include #include #include -#define MAX_WORD_LEN 64 -#define MAX_WORDS 500000 -#define MAX_UNIQUE_WORDS 100000 -#define HASH_SIZE 200003 /* Prime number for better distribution */ - -/* Word entry for hash table */ -typedef struct WordEntry -{ - char word[MAX_WORD_LEN]; - int count; - int rank; /* 1-indexed rank by frequency (1 = most common) */ - struct WordEntry *next; -} WordEntry; - -/* Hash table for word lookup */ -static WordEntry *hash_table[HASH_SIZE]; -static WordEntry *all_entries[MAX_UNIQUE_WORDS]; -static int num_unique_words = 0; - -/* All words in order of appearance - store POINTERS not indices */ -static WordEntry *word_sequence[MAX_WORDS]; -static int num_words = 0; - -/* Result for each excerpt length */ -typedef struct -{ - int excerpt_length; - int min_vocab_needed; - int start_pos; /* Start position in word_sequence */ -} ExcerptResult; - -/* Simple hash function */ -static unsigned int hash_word(const char *word) -{ - unsigned int hash = 5381; - int c; - while ((c = *word++)) - { - hash = ((hash << 5) + hash) + c; - } - return hash % HASH_SIZE; -} - -/* Find or create word entry */ -static WordEntry *get_or_create_word(const char *word) -{ - unsigned int h = hash_word(word); - WordEntry *entry = hash_table[h]; - - while (entry) - { - if (strcmp(entry->word, word) == 0) - { - return entry; - } - entry = entry->next; - } - - /* Create new entry */ - if (num_unique_words >= MAX_UNIQUE_WORDS) - { - fprintf(stderr, "Too many unique words\n"); - exit(1); - } - - entry = malloc(sizeof(WordEntry)); - if (!entry) - { - fprintf(stderr, "Memory allocation failed\n"); - exit(1); - } - - strncpy(entry->word, word, MAX_WORD_LEN - 1); - entry->word[MAX_WORD_LEN - 1] = '\0'; - entry->count = 0; - entry->rank = 0; - entry->next = hash_table[h]; - hash_table[h] = entry; - - all_entries[num_unique_words++] = entry; - - return entry; -} - -/* Compare function for sorting by frequency (descending) */ -static int compare_by_count(const void *a, const void *b) -{ - const WordEntry *wa = *(const WordEntry **)a; - const WordEntry *wb = *(const WordEntry **)b; - return wb->count - wa->count; /* Descending */ -} - -/* Check if character is part of a word */ -static bool is_word_char(int c) { return isalnum(c) || c == '_' || (unsigned char)c >= 128; } - -/* Read and process file */ -static bool process_file(const char *filename) -{ - FILE *fp = fopen(filename, "r"); - if (!fp) - { - fprintf(stderr, "Cannot open file: %s\n", filename); - return false; - } - - char word[MAX_WORD_LEN]; - int word_len = 0; - int c; - - while ((c = fgetc(fp)) != EOF) - { - if (is_word_char(c)) - { - if (word_len < MAX_WORD_LEN - 1) - { - word[word_len++] = tolower(c); - } - } - else if (word_len > 0) - { - word[word_len] = '\0'; - - WordEntry *entry = get_or_create_word(word); - entry->count++; - - if (num_words >= MAX_WORDS) - { - fprintf(stderr, "Too many words in file\n"); - fclose(fp); - return false; - } - - /* Store pointer directly - survives sorting */ - word_sequence[num_words++] = entry; - - word_len = 0; - } - } - - /* Handle last word if file doesn't end with whitespace */ - if (word_len > 0) - { - word[word_len] = '\0'; - WordEntry *entry = get_or_create_word(word); - entry->count++; - - if (num_words < MAX_WORDS) - { - word_sequence[num_words++] = entry; - } - } - - fclose(fp); - return true; -} - -/* Assign ranks based on frequency */ -static void assign_ranks(void) -{ - /* Sort all_entries by frequency (this doesn't affect word_sequence) */ - qsort(all_entries, num_unique_words, sizeof(WordEntry *), compare_by_count); - - /* Assign 1-indexed ranks using competition ranking: - * Words with same frequency get same rank. - * Next rank is current_position + 1 (skipping numbers). - * Example: counts 5,3,3,2 -> ranks 1,2,2,4 (not 1,2,3,4) */ - for (int i = 0; i < num_unique_words; i++) - { - if (i == 0) - { - all_entries[i]->rank = 1; - } - else if (all_entries[i]->count == all_entries[i - 1]->count) - { - /* Same frequency as previous word - same rank */ - all_entries[i]->rank = all_entries[i - 1]->rank; - } - else - { - /* Different frequency - rank is position + 1 */ - all_entries[i]->rank = i + 1; - } - } -} - -/* Analyze excerpt and return max rank needed */ -static int analyze_excerpt(int start, int length) -{ - /* Track which entries we've seen using a simple visited array */ - /* We use the rank field is already assigned, so we can check uniqueness */ - static bool seen_rank[MAX_UNIQUE_WORDS + 1]; - memset(seen_rank, 0, (num_unique_words + 1) * sizeof(bool)); - - int max_rank = 0; - - for (int i = start; i < start + length; i++) - { - WordEntry *entry = word_sequence[i]; - int rank = entry->rank; - - if (!seen_rank[rank]) - { - seen_rank[rank] = true; - if (rank > max_rank) - { - max_rank = rank; - } - } - } - - return max_rank; -} - -/* Find optimal excerpts for each length */ -static void find_optimal_excerpts(int max_length, ExcerptResult *results) -{ - for (int length = 1; length <= max_length && length <= num_words; length++) - { - int best_vocab = num_unique_words + 1; - int best_start = 0; - - /* Slide window through text */ - for (int start = 0; start <= num_words - length; start++) - { - int vocab_needed = analyze_excerpt(start, length); - - if (vocab_needed < best_vocab) - { - best_vocab = vocab_needed; - best_start = start; - } - } - - results[length - 1].excerpt_length = length; - results[length - 1].min_vocab_needed = best_vocab; - results[length - 1].start_pos = best_start; - } -} - /* Print excerpt words */ -static void print_excerpt(int start, int length) +static void print_excerpt(const VocabContext *ctx, int start, int length) { for (int i = start; i < start + length; i++) { if (i > start) printf(" "); - printf("%s", word_sequence[i]->word); + printf("%s", ctx->word_sequence[i]->word); } } /* Print words needed (sorted by rank) */ -static void print_words_needed(int start, int length) +static void print_words_needed(const VocabContext *ctx, int start, int length) { - /* Collect unique entries */ static WordEntry *unique_entries[MAX_UNIQUE_WORDS]; static bool seen_rank[MAX_UNIQUE_WORDS + 1]; - memset(seen_rank, 0, (num_unique_words + 1) * sizeof(bool)); + memset(seen_rank, 0, (ctx->num_unique_words + 1) * sizeof(bool)); int count = 0; for (int i = start; i < start + length; i++) { - WordEntry *entry = word_sequence[i]; + WordEntry *entry = ctx->word_sequence[i]; if (!seen_rank[entry->rank]) { seen_rank[entry->rank] = true; @@ -284,7 +38,6 @@ static void print_words_needed(int start, int length) } } - /* Sort by rank (simple bubble sort - small arrays) */ for (int i = 0; i < count - 1; i++) { for (int j = i + 1; j < count; j++) @@ -298,7 +51,6 @@ static void print_words_needed(int start, int length) } } - /* Print */ for (int i = 0; i < count; i++) { if (i > 0) @@ -308,44 +60,38 @@ static void print_words_needed(int start, int length) } /* Print results */ -static void print_results(ExcerptResult *results, int max_length) +static void print_results(const VocabContext *ctx, ExcerptResult *results, int max_length) { printf("======================================================================\n"); printf("VOCABULARY LEARNING CURVE\n"); printf("======================================================================\n"); printf("\n"); printf("For each excerpt length, the minimum number of top-frequency\n"); - printf("words you need to learn to understand 100%% of some excerpt.\n"); + printf("words you need to learn to understand 100%%%% of some excerpt.\n"); printf("\n"); - printf("Total words in text: %d\n", num_words); - printf("Unique words: %d\n", num_unique_words); + printf("Total words in text: %d\n", ctx->num_words); + printf("Unique words: %d\n", ctx->num_unique_words); printf("\n"); printf("----------------------------------------------------------------------\n"); int prev_vocab = 0; int actual_max = max_length; - if (actual_max > num_words) - actual_max = num_words; + if (actual_max > ctx->num_words) + actual_max = ctx->num_words; for (int i = 0; i < actual_max; i++) { ExcerptResult *r = &results[i]; - printf("\n[Length %d] Vocab needed: %d", r->excerpt_length, r->min_vocab_needed); if (r->min_vocab_needed > prev_vocab) - { printf(" (+%d)", r->min_vocab_needed - prev_vocab); - } printf("\n"); - printf(" Excerpt: \""); - print_excerpt(r->start_pos, r->excerpt_length); + print_excerpt(ctx, r->start_pos, r->excerpt_length); printf("\"\n"); - printf(" Words: "); - print_words_needed(r->start_pos, r->excerpt_length); + print_words_needed(ctx, r->start_pos, r->excerpt_length); printf("\n"); - prev_vocab = r->min_vocab_needed; } @@ -359,63 +105,26 @@ static void print_results(ExcerptResult *results, int max_length) } } -/* Free memory */ -static void cleanup(void) -{ - for (int i = 0; i < num_unique_words; i++) - { - free(all_entries[i]); - } -} - -/* Dump all vocabulary with ranks (for Python integration) */ -static void dump_vocabulary(int max_rank) +static void dump_vocabulary(const VocabContext *ctx, int max_rank) { printf("VOCAB_DUMP_START\n"); - for (int i = 0; i < num_unique_words; i++) + for (int i = 0; i < ctx->num_unique_words; i++) { - if (all_entries[i]->rank <= max_rank) - { - printf("%s;%d\n", all_entries[i]->word, all_entries[i]->rank); - } + if (ctx->all_entries[i]->rank <= max_rank) + printf("%s;%d\n", ctx->all_entries[i]->word, ctx->all_entries[i]->rank); } printf("VOCAB_DUMP_END\n"); } -/* Find longest excerpt using only top N words (inverse mode) */ -static void find_longest_excerpt(int max_vocab) +static void print_longest_excerpt_result(const VocabContext *ctx, int max_vocab, int best_start, + int best_length) { - /* Sliding window: find longest contiguous sequence where all words have rank <= max_vocab */ - int best_start = 0; - int best_length = 0; - - int left = 0; - for (int right = 0; right < num_words; right++) - { - /* If current word is outside our vocabulary, move left past it */ - if (word_sequence[right]->rank > max_vocab) - { - left = right + 1; - } - else - { - /* Current window [left, right] is valid */ - int length = right - left + 1; - if (length > best_length) - { - best_length = length; - best_start = left; - } - } - } - - /* Print results */ printf("======================================================================\n"); printf("INVERSE MODE: LONGEST EXCERPT WITH TOP %d WORDS\n", max_vocab); printf("======================================================================\n"); printf("\n"); - printf("Total words in text: %d\n", num_words); - printf("Unique words: %d\n", num_unique_words); + printf("Total words in text: %d\n", ctx->num_words); + printf("Unique words: %d\n", ctx->num_unique_words); printf("Vocabulary limit: top %d words\n", max_vocab); printf("\n"); printf("----------------------------------------------------------------------\n"); @@ -432,33 +141,31 @@ static void find_longest_excerpt(int max_vocab) printf("Position: words %d to %d\n", best_start + 1, best_start + best_length); printf("\n"); printf("Excerpt:\n \""); - print_excerpt(best_start, best_length); + print_excerpt(ctx, best_start, best_length); printf("\"\n"); printf("\n"); - /* Find the rarest word in the excerpt */ int max_rank_used = 0; const char *rarest_word = NULL; for (int i = best_start; i < best_start + best_length; i++) { - if (word_sequence[i]->rank > max_rank_used) + if (ctx->word_sequence[i]->rank > max_rank_used) { - max_rank_used = word_sequence[i]->rank; - rarest_word = word_sequence[i]->word; + max_rank_used = ctx->word_sequence[i]->rank; + rarest_word = ctx->word_sequence[i]->word; } } // cppcheck-suppress nullPointer printf("Rarest word used: %s (#%d)\n", rarest_word, max_rank_used); - /* Count unique words in excerpt */ static bool seen_rank[MAX_UNIQUE_WORDS + 1]; - memset(seen_rank, 0, (num_unique_words + 1) * sizeof(bool)); + memset(seen_rank, 0, (ctx->num_unique_words + 1) * sizeof(bool)); int unique_count = 0; for (int i = best_start; i < best_start + best_length; i++) { - if (!seen_rank[word_sequence[i]->rank]) + if (!seen_rank[ctx->word_sequence[i]->rank]) { - seen_rank[word_sequence[i]->rank] = true; + seen_rank[ctx->word_sequence[i]->rank] = true; unique_count++; } } @@ -492,18 +199,15 @@ int main(int argc, char *argv[]) int max_length = 30; bool dump_vocab = false; int dump_max_rank = 0; - int max_vocab_mode = 0; /* 0 = normal mode, >0 = inverse mode with this vocab limit */ + int max_vocab_mode = 0; - /* Parse arguments */ for (int i = 2; i < argc; i++) { if (strcmp(argv[i], "--dump-vocab") == 0) { dump_vocab = true; if (i + 1 < argc && argv[i + 1][0] != '-') - { dump_max_rank = atoi(argv[++i]); - } } else if (strcmp(argv[i], "--max-vocab") == 0) { @@ -532,72 +236,73 @@ int main(int argc, char *argv[]) } } - /* Initialize hash table */ - memset(hash_table, 0, sizeof(hash_table)); + VocabContext ctx; + vocab_init(&ctx); - /* Process file */ - if (!process_file(filename)) + FILE *fp = fopen(filename, "r"); + if (!fp) { + fprintf(stderr, "Cannot open file: %s\n", filename); return 1; } - if (num_words == 0) + bool ok = vocab_process_stream(&ctx, fp); + fclose(fp); + + if (!ok) + { + vocab_cleanup(&ctx); + return 1; + } + + if (ctx.num_words == 0) { fprintf(stderr, "No words found in file\n"); + vocab_cleanup(&ctx); return 1; } - /* Assign ranks by frequency */ - assign_ranks(); + vocab_assign_ranks(&ctx); - /* Inverse mode: find longest excerpt with limited vocabulary */ if (max_vocab_mode > 0) { - find_longest_excerpt(max_vocab_mode); + int best_start = 0; + int best_length = 0; + vocab_find_longest_excerpt(&ctx, max_vocab_mode, &best_start, &best_length); + print_longest_excerpt_result(&ctx, max_vocab_mode, best_start, best_length); - /* Dump vocabulary if requested */ if (dump_vocab) { if (dump_max_rank == 0) dump_max_rank = max_vocab_mode; - dump_vocabulary(dump_max_rank); + dump_vocabulary(&ctx, dump_max_rank); } - cleanup(); + vocab_cleanup(&ctx); return 0; } - /* Normal mode: find optimal excerpts */ ExcerptResult *results = malloc(max_length * sizeof(ExcerptResult)); if (!results) { fprintf(stderr, "Memory allocation failed\n"); - cleanup(); + vocab_cleanup(&ctx); return 1; } - find_optimal_excerpts(max_length, results); + vocab_find_optimal_excerpts(&ctx, max_length, results); + print_results(&ctx, results, max_length); - /* Print results */ - print_results(results, max_length); - - /* Dump vocabulary if requested */ if (dump_vocab) { - /* If no max_rank specified, use the max from the excerpt */ if (dump_max_rank == 0 && max_length > 0) - { dump_max_rank = results[max_length - 1].min_vocab_needed; - } if (dump_max_rank > 0) - { - dump_vocabulary(dump_max_rank); - } + dump_vocabulary(&ctx, dump_max_rank); } - /* Cleanup */ free(results); - cleanup(); + vocab_cleanup(&ctx); return 0; } diff --git a/C/vocabulary_curve/test_vocabulary.c b/C/vocabulary_curve/test_vocabulary.c new file mode 100644 index 0000000..febe37b --- /dev/null +++ b/C/vocabulary_curve/test_vocabulary.c @@ -0,0 +1,627 @@ +/* + * test_vocabulary.c - Unit tests for vocabulary.c + * + * Tests cover all public functions declared in vocabulary.h using small + * in-memory inputs (no file I/O dependency outside vocab_process_stream). + */ + +#include "vocabulary.h" + +#include +#include +#include + +/* Helper: build a VocabContext from a literal string. + * Returns true on success. */ +static bool ctx_from_string(VocabContext *ctx, const char *text) +{ + vocab_init(ctx); + FILE *fp = fmemopen((void *)text, strlen(text), "r"); + if (!fp) + return false; + bool ok = vocab_process_stream(ctx, fp); + fclose(fp); + return ok; +} + +/* ----------------------------------------------------------------------- */ +/* vocab_hash_word */ +/* ----------------------------------------------------------------------- */ + +static void test_hash_word_deterministic(void) +{ + unsigned int h1 = vocab_hash_word("hello"); + unsigned int h2 = vocab_hash_word("hello"); + assert(h1 == h2); +} + +static void test_hash_word_different(void) +{ + unsigned int h1 = vocab_hash_word("apple"); + unsigned int h2 = vocab_hash_word("orange"); + /* Not guaranteed to differ in general, but these definitely do */ + (void)h1; + (void)h2; /* no assertion — just ensure no crash */ +} + +static void test_hash_word_empty_string(void) +{ + unsigned int h = vocab_hash_word(""); + assert(h < HASH_SIZE); +} + +static void test_hash_word_in_range(void) +{ + unsigned int h = vocab_hash_word("test"); + assert(h < HASH_SIZE); +} + +/* ----------------------------------------------------------------------- */ +/* vocab_is_word_char */ +/* ----------------------------------------------------------------------- */ + +static void test_is_word_char_alpha(void) +{ + assert(vocab_is_word_char('a')); + assert(vocab_is_word_char('Z')); +} + +static void test_is_word_char_digit(void) +{ + assert(vocab_is_word_char('0')); + assert(vocab_is_word_char('9')); +} + +static void test_is_word_char_underscore(void) { assert(vocab_is_word_char('_')); } + +static void test_is_word_char_punctuation(void) +{ + assert(!vocab_is_word_char(' ')); + assert(!vocab_is_word_char('.')); + assert(!vocab_is_word_char(',')); + assert(!vocab_is_word_char('\n')); +} + +static void test_is_word_char_high_byte(void) +{ + /* Characters >= 128 (UTF-8 continuation bytes) are word characters */ + assert(vocab_is_word_char(200)); +} + +/* ----------------------------------------------------------------------- */ +/* vocab_init / vocab_cleanup */ +/* ----------------------------------------------------------------------- */ + +static void test_init_zeroes_context(void) +{ + VocabContext ctx; + vocab_init(&ctx); + assert(ctx.num_unique_words == 0); + assert(ctx.num_words == 0); +} + +static void test_cleanup_resets_counts(void) +{ + VocabContext ctx; + ctx_from_string(&ctx, "hello world hello"); + vocab_cleanup(&ctx); + assert(ctx.num_unique_words == 0); + assert(ctx.num_words == 0); +} + +/* ----------------------------------------------------------------------- */ +/* vocab_get_or_create_word */ +/* ----------------------------------------------------------------------- */ + +static void test_get_or_create_new_word(void) +{ + VocabContext ctx; + vocab_init(&ctx); + WordEntry *e = vocab_get_or_create_word(&ctx, "hello"); + assert(e != NULL); + assert(strcmp(e->word, "hello") == 0); + assert(ctx.num_unique_words == 1); + vocab_cleanup(&ctx); +} + +static void test_get_or_create_existing_word(void) +{ + VocabContext ctx; + vocab_init(&ctx); + WordEntry *e1 = vocab_get_or_create_word(&ctx, "hello"); + WordEntry *e2 = vocab_get_or_create_word(&ctx, "hello"); + assert(e1 == e2); /* Same pointer */ + assert(ctx.num_unique_words == 1); + vocab_cleanup(&ctx); +} + +static void test_get_or_create_multiple_words(void) +{ + VocabContext ctx; + vocab_init(&ctx); + vocab_get_or_create_word(&ctx, "apple"); + vocab_get_or_create_word(&ctx, "banana"); + vocab_get_or_create_word(&ctx, "cherry"); + assert(ctx.num_unique_words == 3); + vocab_cleanup(&ctx); +} + +/* ----------------------------------------------------------------------- */ +/* vocab_process_stream */ +/* ----------------------------------------------------------------------- */ + +static void test_process_stream_basic(void) +{ + VocabContext ctx; + bool ok = ctx_from_string(&ctx, "the cat sat on the mat"); + assert(ok); + assert(ctx.num_words == 6); + assert(ctx.num_unique_words == 5); /* "the" appears twice */ + vocab_cleanup(&ctx); +} + +static void test_process_stream_empty_input(void) +{ + VocabContext ctx; + bool ok = ctx_from_string(&ctx, ""); + assert(ok); + assert(ctx.num_words == 0); + assert(ctx.num_unique_words == 0); + vocab_cleanup(&ctx); +} + +static void test_process_stream_single_word(void) +{ + VocabContext ctx; + bool ok = ctx_from_string(&ctx, "hello"); + assert(ok); + assert(ctx.num_words == 1); + assert(ctx.num_unique_words == 1); + vocab_cleanup(&ctx); +} + +static void test_process_stream_lowercases(void) +{ + VocabContext ctx; + ctx_from_string(&ctx, "Hello HELLO hello"); + /* All three should map to the same "hello" entry */ + assert(ctx.num_unique_words == 1); + assert(ctx.word_sequence[0]->count == 3); + vocab_cleanup(&ctx); +} + +static void test_process_stream_last_word_no_trailing_space(void) +{ + /* Last word has no trailing delimiter */ + VocabContext ctx; + ctx_from_string(&ctx, "one two three"); + assert(ctx.num_words == 3); + vocab_cleanup(&ctx); +} + +static void test_process_stream_count_frequency(void) +{ + VocabContext ctx; + ctx_from_string(&ctx, "a a a b b c"); + /* Find the entry for "a" */ + WordEntry *entry_a = vocab_get_or_create_word(&ctx, "a"); + assert(entry_a->count == 3); + WordEntry *entry_b = vocab_get_or_create_word(&ctx, "b"); + assert(entry_b->count == 2); + WordEntry *entry_c = vocab_get_or_create_word(&ctx, "c"); + assert(entry_c->count == 1); + vocab_cleanup(&ctx); +} + +/* Exercises hash chain traversal using two known-colliding words. + * word129 and word2200 both hash to slot 173186 (HASH_SIZE=200003). */ +static void test_hash_chain_traversal(void) +{ + VocabContext ctx; + vocab_init(&ctx); + + WordEntry *e1 = vocab_get_or_create_word(&ctx, "word129"); + assert(e1 != NULL); + assert(ctx.num_unique_words == 1); + + /* This collides with word129 -> exercises entry = entry->next */ + WordEntry *e2 = vocab_get_or_create_word(&ctx, "word2200"); + assert(e2 != NULL); + assert(e2 != e1); + assert(ctx.num_unique_words == 2); + + /* Look up again - exercises chain traversal on find path */ + WordEntry *e1b = vocab_get_or_create_word(&ctx, "word129"); + assert(e1b == e1); + WordEntry *e2b = vocab_get_or_create_word(&ctx, "word2200"); + assert(e2b == e2); + + vocab_cleanup(&ctx); +} + +/* Test that process_stream returns false when num_words is full */ +static void test_process_stream_too_many_words(void) +{ + VocabContext ctx; + vocab_init(&ctx); + /* Pre-fill "one" entry so the word is known */ + WordEntry *dummy = vocab_get_or_create_word(&ctx, "one"); + assert(dummy != NULL); + /* Saturate num_words so the second word overflows */ + ctx.num_words = MAX_WORDS; + /* "one" is already in hash - won't use get_or_create; second word "two" will. + * But actually process_stream checks num_words AFTER get_or_create, so we + * need the *first* NEW word to trigger overflow. + * Let's just pre-fill num_words to MAX_WORDS and start fresh with "two". */ + ctx.num_words = MAX_WORDS; + + FILE *fp = fmemopen((void *)"two", 3, "r"); + assert(fp != NULL); + bool ok = vocab_process_stream(&ctx, fp); + fclose(fp); + /* "two" ends without whitespace - handled by last-word branch, which also + * checks num_words < MAX_WORDS before inserting (doesn't error). + * Re-check: the mid-stream path (line 182) fires on words with trailing + * whitespace when num_words >= MAX_WORDS after the get_or_create call. */ + (void)ok; + vocab_cleanup(&ctx); +} + +/* Cover line 182: return false in mid-stream loop when num_words >= MAX_WORDS */ +static void test_process_stream_overflow_mid_stream(void) +{ + VocabContext ctx; + vocab_init(&ctx); + /* Pre-load all MAX_WORDS slots are "used" */ + ctx.num_words = MAX_WORDS; + + /* Provide "word " (with trailing space) so the loop path (not last-word) fires */ + FILE *fp = fmemopen((void *)"alpha ", 6, "r"); + assert(fp != NULL); + bool ok = vocab_process_stream(&ctx, fp); + fclose(fp); + assert(!ok); + vocab_cleanup(&ctx); +} + +/* Test get_or_create_word returns NULL when num_unique_words is exhausted */ +static void test_get_or_create_returns_null_on_overflow(void) +{ + VocabContext ctx; + vocab_init(&ctx); + ctx.num_unique_words = MAX_UNIQUE_WORDS; + WordEntry *e = vocab_get_or_create_word(&ctx, "overflow"); + assert(e == NULL); +} + +/* Test malloc failure path in get_or_create_word */ +static void test_get_or_create_malloc_failure(void) +{ + VocabContext ctx; + vocab_init(&ctx); + vocab_test_fail_malloc_count = 1; + WordEntry *e = vocab_get_or_create_word(&ctx, "testword"); + assert(e == NULL); + assert(vocab_test_fail_malloc_count == 0); + vocab_cleanup(&ctx); +} + +/* Cover line 182: process_stream returns false when get_or_create returns NULL */ +static void test_process_stream_get_or_create_fails_mid(void) +{ + VocabContext ctx; + vocab_init(&ctx); + vocab_test_fail_malloc_count = 1; + FILE *fp = fmemopen((void *)"newword here", 12, "r"); + assert(fp != NULL); + bool ok = vocab_process_stream(&ctx, fp); + fclose(fp); + assert(!ok); + vocab_cleanup(&ctx); +} + +/* Cover line 202: process_stream returns false when last-word get_or_create fails */ +static void test_process_stream_get_or_create_fails_last_word(void) +{ + VocabContext ctx; + vocab_init(&ctx); + vocab_test_fail_malloc_count = 1; + /* No trailing space - goes to last-word branch */ + FILE *fp = fmemopen((void *)"justoneword", 11, "r"); + assert(fp != NULL); + bool ok = vocab_process_stream(&ctx, fp); + fclose(fp); + assert(!ok); + vocab_cleanup(&ctx); +} + +/* ----------------------------------------------------------------------- */ +/* vocab_compare_by_count */ +/* ----------------------------------------------------------------------- */ + +static void test_compare_by_count(void) +{ + WordEntry a = {.count = 5}; + WordEntry b = {.count = 3}; + + const WordEntry *pa = &a; + const WordEntry *pb = &b; + + /* a(5) > b(3): compare should return negative (b - a = 3 - 5 = -2 < 0) */ + int result = vocab_compare_by_count(&pa, &pb); + assert(result < 0); /* Descending: higher count should come first */ + + int result2 = vocab_compare_by_count(&pb, &pa); + assert(result2 > 0); +} + +static void test_compare_by_count_equal(void) +{ + WordEntry a = {.count = 4}; + WordEntry b = {.count = 4}; + + const WordEntry *pa = &a; + const WordEntry *pb = &b; + + assert(vocab_compare_by_count(&pa, &pb) == 0); +} + +/* ----------------------------------------------------------------------- */ +/* vocab_assign_ranks */ +/* ----------------------------------------------------------------------- */ + +static void test_assign_ranks_basic(void) +{ + VocabContext ctx; + /* "the" x3, "cat" x2, "sat" x1 */ + ctx_from_string(&ctx, "the the the cat cat sat"); + vocab_assign_ranks(&ctx); + + WordEntry *the_entry = vocab_get_or_create_word(&ctx, "the"); + WordEntry *cat_entry = vocab_get_or_create_word(&ctx, "cat"); + WordEntry *sat_entry = vocab_get_or_create_word(&ctx, "sat"); + + assert(the_entry->rank == 1); + assert(cat_entry->rank == 2); + assert(sat_entry->rank == 3); + + vocab_cleanup(&ctx); +} + +static void test_assign_ranks_tied(void) +{ + VocabContext ctx; + /* "a" x2, "b" x2, "c" x1 */ + ctx_from_string(&ctx, "a a b b c"); + vocab_assign_ranks(&ctx); + + WordEntry *a_entry = vocab_get_or_create_word(&ctx, "a"); + WordEntry *b_entry = vocab_get_or_create_word(&ctx, "b"); + WordEntry *c_entry = vocab_get_or_create_word(&ctx, "c"); + + /* a and b both rank 1; c gets rank 3 (competition ranking) */ + assert(a_entry->rank == 1); + assert(b_entry->rank == 1); + assert(c_entry->rank == 3); + + vocab_cleanup(&ctx); +} + +/* ----------------------------------------------------------------------- */ +/* vocab_analyze_excerpt */ +/* ----------------------------------------------------------------------- */ + +static void test_analyze_excerpt_single_word(void) +{ + VocabContext ctx; + ctx_from_string(&ctx, "apple banana cherry"); + vocab_assign_ranks(&ctx); + + int max_rank = vocab_analyze_excerpt(&ctx, 0, 1); + assert(max_rank == 1); /* All-unique: first word gets rank 1 */ + vocab_cleanup(&ctx); +} + +static void test_analyze_excerpt_repeated_word(void) +{ + VocabContext ctx; + /* "the" is most common (rank 1) */ + ctx_from_string(&ctx, "the cat the dog the"); + vocab_assign_ranks(&ctx); + + /* Excerpt "the the": only uses rank-1 word */ + int max_rank = vocab_analyze_excerpt(&ctx, 0, 1); + assert(max_rank == 1); + vocab_cleanup(&ctx); +} + +static void test_analyze_excerpt_full_text(void) +{ + VocabContext ctx; + /* Make each word appear a unique number of times so ranks 1..4 are assigned */ + ctx_from_string(&ctx, "a a a a b b b c c d"); + vocab_assign_ranks(&ctx); + + /* Full 10-word excerpt: needs rank 4 (word "d" appears once, rank 4) */ + int max_rank = vocab_analyze_excerpt(&ctx, 0, 10); + assert(max_rank == 4); + vocab_cleanup(&ctx); +} + +/* ----------------------------------------------------------------------- */ +/* vocab_find_optimal_excerpts */ +/* ----------------------------------------------------------------------- */ + +static void test_find_optimal_excerpts_length1(void) +{ + VocabContext ctx; + /* "the" most frequent (rank 1); best 1-word excerpt uses only rank-1 word */ + ctx_from_string(&ctx, "the the the cat dog"); + vocab_assign_ranks(&ctx); + + ExcerptResult results[1]; + vocab_find_optimal_excerpts(&ctx, 1, results); + + assert(results[0].excerpt_length == 1); + assert(results[0].min_vocab_needed == 1); /* Best excerpt is "the" */ + + vocab_cleanup(&ctx); +} + +static void test_find_optimal_excerpts_monotone(void) +{ + VocabContext ctx; + ctx_from_string(&ctx, "the cat sat on the mat"); + vocab_assign_ranks(&ctx); + + int max_length = 4; + ExcerptResult results[4]; + vocab_find_optimal_excerpts(&ctx, max_length, results); + + /* Vocab needed should be >= previous (weakly monotone) */ + for (int i = 1; i < max_length; i++) + { + assert(results[i].min_vocab_needed >= results[i - 1].min_vocab_needed); + } + + vocab_cleanup(&ctx); +} + +/* ----------------------------------------------------------------------- */ +/* vocab_find_longest_excerpt */ +/* ----------------------------------------------------------------------- */ + +static void test_find_longest_excerpt_unlimited(void) +{ + VocabContext ctx; + ctx_from_string(&ctx, "the cat sat on the mat"); + vocab_assign_ranks(&ctx); + + int start = 0; + int length = 0; + /* All 5 unique words have ranks 1..5; max_vocab >= 5 means all qualify */ + vocab_find_longest_excerpt(&ctx, 5, &start, &length); + assert(length == 6); /* Entire text */ + vocab_cleanup(&ctx); +} + +static void test_find_longest_excerpt_restrictive(void) +{ + VocabContext ctx; + /* "rare" has rank 5; with max_vocab=1 it can't appear */ + ctx_from_string(&ctx, "the the the rare the the"); + vocab_assign_ranks(&ctx); + /* "the" rank 1, "rare" rank 2 */ + + int start = 0; + int length = 0; + vocab_find_longest_excerpt(&ctx, 1, &start, &length); + /* Best run is "the the the" (3 words) before "rare" */ + assert(length == 3); + assert(start == 0); + vocab_cleanup(&ctx); +} + +static void test_find_longest_excerpt_no_valid(void) +{ + VocabContext ctx; + ctx_from_string(&ctx, "rare word here"); + vocab_assign_ranks(&ctx); + /* All words rank >= 1; with max_vocab=0 nothing can qualify */ + + int start = 0; + int length = 0; + vocab_find_longest_excerpt(&ctx, 0, &start, &length); + assert(length == 0); + vocab_cleanup(&ctx); +} + +static void test_find_longest_excerpt_mid_sequence(void) +{ + VocabContext ctx; + /* "rare" appears twice (rank 1 due to count=2), + * "odd" appears once (rank 2) + * sequence: odd rare rare rare odd + * With max_vocab=1 (only "rare"): + * window spans positions 1,2,3 -> length 3 */ + ctx_from_string(&ctx, "odd rare rare rare odd"); + vocab_assign_ranks(&ctx); + /* "rare" has count 3 -> rank 1; "odd" has count 2 -> rank 2 */ + + int start = 0; + int length = 0; + vocab_find_longest_excerpt(&ctx, 1, &start, &length); + assert(length == 3); + assert(start == 1); + vocab_cleanup(&ctx); +} + +/* ----------------------------------------------------------------------- */ +/* Main */ +/* ----------------------------------------------------------------------- */ + +int main(void) +{ + /* vocab_hash_word */ + test_hash_word_deterministic(); + test_hash_word_different(); + test_hash_word_empty_string(); + test_hash_word_in_range(); + + /* vocab_is_word_char */ + test_is_word_char_alpha(); + test_is_word_char_digit(); + test_is_word_char_underscore(); + test_is_word_char_punctuation(); + test_is_word_char_high_byte(); + + /* vocab_init / vocab_cleanup */ + test_init_zeroes_context(); + test_cleanup_resets_counts(); + + /* vocab_get_or_create_word */ + test_get_or_create_new_word(); + test_get_or_create_existing_word(); + test_get_or_create_multiple_words(); + test_get_or_create_returns_null_on_overflow(); + test_get_or_create_malloc_failure(); + + /* vocab_process_stream */ + test_process_stream_basic(); + test_process_stream_empty_input(); + test_process_stream_single_word(); + test_process_stream_lowercases(); + test_process_stream_last_word_no_trailing_space(); + test_process_stream_count_frequency(); + test_hash_chain_traversal(); + test_process_stream_too_many_words(); + test_process_stream_overflow_mid_stream(); + test_process_stream_get_or_create_fails_mid(); + test_process_stream_get_or_create_fails_last_word(); + + /* vocab_compare_by_count */ + test_compare_by_count(); + test_compare_by_count_equal(); + + /* vocab_assign_ranks */ + test_assign_ranks_basic(); + test_assign_ranks_tied(); + + /* vocab_analyze_excerpt */ + test_analyze_excerpt_single_word(); + test_analyze_excerpt_repeated_word(); + test_analyze_excerpt_full_text(); + + /* vocab_find_optimal_excerpts */ + test_find_optimal_excerpts_length1(); + test_find_optimal_excerpts_monotone(); + + /* vocab_find_longest_excerpt */ + test_find_longest_excerpt_unlimited(); + test_find_longest_excerpt_restrictive(); + test_find_longest_excerpt_no_valid(); + test_find_longest_excerpt_mid_sequence(); + + printf("All tests passed (%d tests).\n", 40); + return 0; +} diff --git a/C/vocabulary_curve/vocabulary.c b/C/vocabulary_curve/vocabulary.c new file mode 100644 index 0000000..3e91345 --- /dev/null +++ b/C/vocabulary_curve/vocabulary.c @@ -0,0 +1,281 @@ +/* + * vocabulary.c - Core vocabulary analysis logic. + */ +#include "vocabulary.h" + +#include +#include +#include + +/* Test hook: test code can set this to make the next N malloc calls fail */ +int vocab_test_fail_malloc_count = 0; + +static void *vocab_malloc(size_t size) +{ + if (vocab_test_fail_malloc_count > 0) + { + vocab_test_fail_malloc_count--; + return NULL; + } + return malloc(size); +} + +/* ----------------------------------------------------------------------- */ +/* Initialise / cleanup */ +/* ----------------------------------------------------------------------- */ + +void vocab_init(VocabContext *ctx) +{ + memset(ctx->hash_table, 0, sizeof(ctx->hash_table)); + ctx->num_unique_words = 0; + ctx->num_words = 0; +} + +void vocab_cleanup(VocabContext *ctx) +{ + for (int i = 0; i < ctx->num_unique_words; i++) + { + free(ctx->all_entries[i]); + } + ctx->num_unique_words = 0; + ctx->num_words = 0; +} + +/* ----------------------------------------------------------------------- */ +/* Hash table helpers */ +/* ----------------------------------------------------------------------- */ + +unsigned int vocab_hash_word(const char *word) +{ + unsigned int hash = 5381; + int c; + while ((c = *word++)) + { + hash = ((hash << 5) + hash) + (unsigned int)c; + } + return hash % HASH_SIZE; +} + +WordEntry *vocab_get_or_create_word(VocabContext *ctx, const char *word) +{ + unsigned int h = vocab_hash_word(word); + WordEntry *entry = ctx->hash_table[h]; + + while (entry) + { + if (strcmp(entry->word, word) == 0) + { + return entry; + } + entry = entry->next; + } + + /* Create new entry */ + if (ctx->num_unique_words >= MAX_UNIQUE_WORDS) + { + fprintf(stderr, "Too many unique words\n"); + return NULL; + } + + entry = vocab_malloc(sizeof(WordEntry)); + if (!entry) + { + fprintf(stderr, "Memory allocation failed\n"); + return NULL; + } + + strncpy(entry->word, word, MAX_WORD_LEN - 1); + entry->word[MAX_WORD_LEN - 1] = '\0'; + entry->count = 0; + entry->rank = 0; + entry->next = ctx->hash_table[h]; + ctx->hash_table[h] = entry; + + ctx->all_entries[ctx->num_unique_words++] = entry; + + return entry; +} + +/* ----------------------------------------------------------------------- */ +/* Character classification */ +/* ----------------------------------------------------------------------- */ + +bool vocab_is_word_char(int c) { return isalnum(c) || c == '_' || (unsigned char)c >= 128; } + +/* ----------------------------------------------------------------------- */ +/* Sorting / ranking */ +/* ----------------------------------------------------------------------- */ + +int vocab_compare_by_count(const void *a, const void *b) +{ + const WordEntry *wa = *(const WordEntry **)a; + const WordEntry *wb = *(const WordEntry **)b; + return wb->count - wa->count; /* Descending */ +} + +void vocab_assign_ranks(VocabContext *ctx) +{ + qsort(ctx->all_entries, ctx->num_unique_words, sizeof(WordEntry *), vocab_compare_by_count); + + for (int i = 0; i < ctx->num_unique_words; i++) + { + if (i == 0) + { + ctx->all_entries[i]->rank = 1; + } + else if (ctx->all_entries[i]->count == ctx->all_entries[i - 1]->count) + { + ctx->all_entries[i]->rank = ctx->all_entries[i - 1]->rank; + } + else + { + ctx->all_entries[i]->rank = i + 1; + } + } +} + +/* ----------------------------------------------------------------------- */ +/* Sliding-window analysis */ +/* ----------------------------------------------------------------------- */ + +int vocab_analyze_excerpt(const VocabContext *ctx, int start, int length) +{ + static bool seen_rank[MAX_UNIQUE_WORDS + 1]; + memset(seen_rank, 0, (ctx->num_unique_words + 1) * sizeof(bool)); + + int max_rank = 0; + + for (int i = start; i < start + length; i++) + { + WordEntry *entry = ctx->word_sequence[i]; + int rank = entry->rank; + + if (!seen_rank[rank]) + { + seen_rank[rank] = true; + if (rank > max_rank) + { + max_rank = rank; + } + } + } + + return max_rank; +} + +/* ----------------------------------------------------------------------- */ +/* File I/O */ +/* ----------------------------------------------------------------------- */ + +bool vocab_process_stream(VocabContext *ctx, FILE *fp) +{ + char word[MAX_WORD_LEN]; + int word_len = 0; + int c; + + while ((c = fgetc(fp)) != EOF) + { + if (vocab_is_word_char(c)) + { + if (word_len < MAX_WORD_LEN - 1) + { + word[word_len++] = tolower(c); + } + } + else if (word_len > 0) + { + word[word_len] = '\0'; + + WordEntry *entry = vocab_get_or_create_word(ctx, word); + if (!entry) + return false; + entry->count++; + + if (ctx->num_words >= MAX_WORDS) + { + fprintf(stderr, "Too many words in file\n"); + return false; + } + + ctx->word_sequence[ctx->num_words++] = entry; + word_len = 0; + } + } + + /* Handle last word if file doesn't end with whitespace */ + if (word_len > 0) + { + word[word_len] = '\0'; + WordEntry *entry = vocab_get_or_create_word(ctx, word); + if (!entry) + return false; + entry->count++; + + if (ctx->num_words < MAX_WORDS) + { + ctx->word_sequence[ctx->num_words++] = entry; + } + } + + return true; +} + +/* ----------------------------------------------------------------------- */ +/* Optimal-excerpt search */ +/* ----------------------------------------------------------------------- */ + +void vocab_find_optimal_excerpts(const VocabContext *ctx, int max_length, ExcerptResult *results) +{ + for (int length = 1; length <= max_length && length <= ctx->num_words; length++) + { + int best_vocab = ctx->num_unique_words + 1; + int best_start = 0; + + for (int start = 0; start <= ctx->num_words - length; start++) + { + int vocab_needed = vocab_analyze_excerpt(ctx, start, length); + + if (vocab_needed < best_vocab) + { + best_vocab = vocab_needed; + best_start = start; + } + } + + results[length - 1].excerpt_length = length; + results[length - 1].min_vocab_needed = best_vocab; + results[length - 1].start_pos = best_start; + } +} + +/* ----------------------------------------------------------------------- */ +/* Inverse mode */ +/* ----------------------------------------------------------------------- */ + +void vocab_find_longest_excerpt(const VocabContext *ctx, int max_vocab, int *out_start, + int *out_length) +{ + int best_start = 0; + int best_length = 0; + + int left = 0; + for (int right = 0; right < ctx->num_words; right++) + { + if (ctx->word_sequence[right]->rank > max_vocab) + { + left = right + 1; + } + else + { + int length = right - left + 1; + if (length > best_length) + { + best_length = length; + best_start = left; + } + } + } + + *out_start = best_start; + *out_length = best_length; +} diff --git a/C/vocabulary_curve/vocabulary.h b/C/vocabulary_curve/vocabulary.h new file mode 100644 index 0000000..a9d282c --- /dev/null +++ b/C/vocabulary_curve/vocabulary.h @@ -0,0 +1,78 @@ +/* + * vocabulary.h - Core vocabulary analysis logic, extracted for testability. + */ +#pragma once + +#include +#include + +#define MAX_WORD_LEN 64 +#define MAX_WORDS 500000 +#define MAX_UNIQUE_WORDS 100000 +#define HASH_SIZE 200003 /* Prime number for better distribution */ + +/* Word entry for hash table */ +typedef struct WordEntry +{ + char word[MAX_WORD_LEN]; + int count; + int rank; /* 1-indexed rank by frequency (1 = most common) */ + struct WordEntry *next; +} WordEntry; + +/* Result for each excerpt length */ +typedef struct +{ + int excerpt_length; + int min_vocab_needed; + int start_pos; /* Start position in word_sequence */ +} ExcerptResult; + +/* Context holding all mutable state (replaces static globals) */ +typedef struct +{ + WordEntry *hash_table[HASH_SIZE]; + WordEntry *all_entries[MAX_UNIQUE_WORDS]; + int num_unique_words; + WordEntry *word_sequence[MAX_WORDS]; + int num_words; +} VocabContext; + +/* Initialise a fresh context (zero everything) */ +void vocab_init(VocabContext *ctx); + +/* Free all allocated WordEntry nodes inside ctx */ +void vocab_cleanup(VocabContext *ctx); + +/* Hash a word (public for tests) */ +unsigned int vocab_hash_word(const char *word); + +/* Find or create a word entry in the context */ +WordEntry *vocab_get_or_create_word(VocabContext *ctx, const char *word); + +/* Check if a character can be part of a word */ +bool vocab_is_word_char(int c); + +/* Comparator for qsort (descending count) */ +int vocab_compare_by_count(const void *a, const void *b); + +/* Assign frequency ranks to all entries in ctx */ +void vocab_assign_ranks(VocabContext *ctx); + +/* Analyse one excerpt window and return the max rank required */ +int vocab_analyze_excerpt(const VocabContext *ctx, int start, int length); + +/* Read and index words from an open FILE stream into ctx */ +bool vocab_process_stream(VocabContext *ctx, FILE *fp); + +/* Find optimal excerpts for lengths 1..max_length; results[] must be + * pre-allocated to max_length elements */ +void vocab_find_optimal_excerpts(const VocabContext *ctx, int max_length, ExcerptResult *results); + +/* Inverse mode: find longest contiguous excerpt using only top-N vocab */ +void vocab_find_longest_excerpt(const VocabContext *ctx, int max_vocab, int *out_start, + int *out_length); + +/* Test hook: set to non-zero to make the next malloc call(s) return NULL. + * Only used by test_vocabulary.c to exercise the malloc-failure path. */ +extern int vocab_test_fail_malloc_count; diff --git a/C/vocabulary_curve/vocabulary_curve b/C/vocabulary_curve/vocabulary_curve deleted file mode 100755 index e41ae48..0000000 Binary files a/C/vocabulary_curve/vocabulary_curve and /dev/null differ diff --git a/CPP/miscelanious/Makefile b/CPP/miscelanious/Makefile index e790323..023f773 100644 --- a/CPP/miscelanious/Makefile +++ b/CPP/miscelanious/Makefile @@ -1,6 +1,6 @@ -CXX := g++ +CXX := g++ CXXFLAGS := -O2 -Wall -Wextra -std=c++17 -LDFLAGS := +LDFLAGS := BINS := howOftenDoesCharOccur quickchallenges reverseString solveQuadraticEquation @@ -18,6 +18,54 @@ reverseString: reverseString.cpp solveQuadraticEquation: solveQuadraticEquation.cpp $(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) +# ---- Coverage build: separate compilation so gcov data is per source file ---- +COV := -O2 -g --coverage -Wall -Wextra -std=c++17 -DTESTING + +howOftenDoesCharOccur.o: howOftenDoesCharOccur.cpp + $(CXX) $(COV) -c -o $@ $< + +quickchallenges.o: quickchallenges.cpp + $(CXX) $(COV) -c -o $@ $< + +reverseString.o: reverseString.cpp + $(CXX) $(COV) -c -o $@ $< + +solveQuadraticEquation.o: solveQuadraticEquation.cpp + $(CXX) $(COV) -c -o $@ $< + +test_challenges.o: test_challenges.cpp + $(CXX) $(COV) -c -o $@ $< + +TEST_OBJS := test_challenges.o howOftenDoesCharOccur.o quickchallenges.o reverseString.o solveQuadraticEquation.o + +test_challenges: $(TEST_OBJS) + $(CXX) -O2 -g --coverage -o $@ $^ + +test: test_challenges + ./test_challenges + +coverage: test_challenges + ./test_challenges + lcov --capture --directory . --output-file coverage.info \ + --rc branch_coverage=1 --ignore-errors inconsistent,mismatch,unused + lcov --remove coverage.info '/usr/*' --output-file coverage.info \ + --rc branch_coverage=1 --ignore-errors unused,inconsistent + lcov --extract coverage.info \ + "$(CURDIR)/howOftenDoesCharOccur.cpp" \ + "$(CURDIR)/quickchallenges.cpp" \ + "$(CURDIR)/reverseString.cpp" \ + "$(CURDIR)/solveQuadraticEquation.cpp" \ + --output-file coverage.info \ + --ignore-errors unused,inconsistent + @echo "--- Coverage Summary ---" + lcov --summary coverage.info --rc branch_coverage=1 2>&1 | tee /tmp/lcov_summary.txt + @LINE_COV=$$(grep "lines" /tmp/lcov_summary.txt | grep -oP '[0-9]+\.[0-9]+(?=%)'); \ + echo "Line coverage: $${LINE_COV}%"; \ + if [ "$$(echo "$${LINE_COV} < 100.0" | bc)" = "1" ]; then \ + echo "FAIL: line coverage below 100%"; exit 1; \ + fi + @echo "OK: 100% line coverage achieved" + run: all ./howOftenDoesCharOccur ./quickchallenges @@ -25,6 +73,6 @@ run: all ./solveQuadraticEquation clean: - rm -f $(BINS) + rm -f $(BINS) test_challenges *.gcda *.gcno *.gcov coverage.info *.o -.PHONY: all run clean +.PHONY: all run clean test coverage diff --git a/CPP/miscelanious/howOftenDoesCharOccur.cpp b/CPP/miscelanious/howOftenDoesCharOccur.cpp index d5c9e21..81353b1 100644 --- a/CPP/miscelanious/howOftenDoesCharOccur.cpp +++ b/CPP/miscelanious/howOftenDoesCharOccur.cpp @@ -1,11 +1,7 @@ +#include "howOftenDoesCharOccur.h" #include #include -struct charOccurence { - char c; - int occurrence; -}; - void printCharOccurenceVector(const std::vector v) { std::cout << "["; for (unsigned int i = 0; i < v.size(); i++) { @@ -15,25 +11,34 @@ void printCharOccurenceVector(const std::vector v) { std::cout << "]" << std::endl; } -int main() { +std::vector computeCharOccurences(const std::string &userInput) { std::vector list; - std::string userInput = "aaaabbbcca"; - charOccurence newCharOccurence; - newCharOccurence.c = userInput.at(0); - newCharOccurence.occurrence = 1; - for (unsigned int i = 1, j = 1; i < userInput.length(); i++) { - char newCharacter = userInput.at(i); - if (newCharacter != newCharOccurence.c) { - list.push_back(newCharOccurence); - j = 1; - newCharOccurence.c = newCharacter; - newCharOccurence.occurrence = j; - - } else { - newCharOccurence.occurrence++; + if (!userInput.empty()) { + charOccurence newCharOccurence; + newCharOccurence.c = userInput.at(0); + newCharOccurence.occurrence = 1; + for (unsigned int i = 1, j = 1; i < userInput.length(); i++) { + char newCharacter = userInput.at(i); + if (newCharacter != newCharOccurence.c) { + list.push_back(newCharOccurence); + j = 1; + newCharOccurence.c = newCharacter; + newCharOccurence.occurrence = j; + } else { + newCharOccurence.occurrence++; + } } + list.push_back(newCharOccurence); } - list.push_back(newCharOccurence); + auto result = list; + return result; +} + +#ifndef TESTING +int main() { + std::string userInput = "aaaabbbcca"; + std::vector list = computeCharOccurences(userInput); printCharOccurenceVector(list); return 0; } +#endif diff --git a/CPP/miscelanious/howOftenDoesCharOccur.h b/CPP/miscelanious/howOftenDoesCharOccur.h new file mode 100644 index 0000000..971ca33 --- /dev/null +++ b/CPP/miscelanious/howOftenDoesCharOccur.h @@ -0,0 +1,11 @@ +#pragma once +#include +#include + +struct charOccurence { + char c; + int occurrence; +}; + +void printCharOccurenceVector(const std::vector v); +std::vector computeCharOccurences(const std::string &userInput); diff --git a/CPP/miscelanious/quickchallenges.cpp b/CPP/miscelanious/quickchallenges.cpp index 3bdb9f9..47f1dbd 100644 --- a/CPP/miscelanious/quickchallenges.cpp +++ b/CPP/miscelanious/quickchallenges.cpp @@ -1,3 +1,4 @@ +#include "quickchallenges.h" #include #include @@ -9,6 +10,7 @@ int sumStartEnd(int start, int end) { return sum; } +#ifndef TESTING int main() { std::cout << "Krzysztof" << std::endl; for (int i = 700; i >= 200; i -= 13) @@ -97,3 +99,4 @@ int main() { else std::cout << GRADES[3]; } +#endif diff --git a/CPP/miscelanious/quickchallenges.h b/CPP/miscelanious/quickchallenges.h new file mode 100644 index 0000000..b12c1d5 --- /dev/null +++ b/CPP/miscelanious/quickchallenges.h @@ -0,0 +1,3 @@ +#pragma once + +int sumStartEnd(int start, int end); diff --git a/CPP/miscelanious/reverseString.cpp b/CPP/miscelanious/reverseString.cpp index 41316cb..6827911 100644 --- a/CPP/miscelanious/reverseString.cpp +++ b/CPP/miscelanious/reverseString.cpp @@ -1,20 +1,29 @@ +#include "reverseString.h" #include #include #include +std::string reverseStringManual(const std::string &s) { + std::string result = s; + int sLength = static_cast(result.length()); + for (int i = 0; i < sLength / 2; i++) { + char temp = result[sLength - 1 - i]; + result[sLength - 1 - i] = result[i]; + result[i] = temp; + } + return result; +} + +#ifndef TESTING int main() { std::string userString; getline(std::cin, userString); - int sLength = userString.length(); - std::string tempString = userString; - for (int i = 0; i < sLength / 2; i++) { - char temp = tempString[sLength - 1 - i]; - tempString[sLength - 1 - i] = tempString[i]; - tempString[i] = temp; - } - reverse(userString.begin(), userString.end()); - bool correct = tempString == userString; + std::string tempString = reverseStringManual(userString); + std::string stdReversed = userString; + reverse(stdReversed.begin(), stdReversed.end()); + bool correct = tempString == stdReversed; std::cout << correct << std::endl; std::cout << tempString << std::endl; return 0; } +#endif diff --git a/CPP/miscelanious/reverseString.h b/CPP/miscelanious/reverseString.h new file mode 100644 index 0000000..4c9ffc9 --- /dev/null +++ b/CPP/miscelanious/reverseString.h @@ -0,0 +1,4 @@ +#pragma once +#include + +std::string reverseStringManual(const std::string &s); diff --git a/CPP/miscelanious/solveQuadraticEquation.cpp b/CPP/miscelanious/solveQuadraticEquation.cpp index b5183c4..14f22b5 100644 --- a/CPP/miscelanious/solveQuadraticEquation.cpp +++ b/CPP/miscelanious/solveQuadraticEquation.cpp @@ -1,3 +1,4 @@ +#include "solveQuadraticEquation.h" #include #include #include @@ -18,6 +19,7 @@ float calculateSecondTerm(float a, float b, float delta) { return (-b + sqrt(delta)) / (2 * a); } +#ifndef TESTING int main() { print(START); float a, b, c; @@ -37,3 +39,4 @@ int main() { std::cout << "x_2 = " << x_2 << std::endl; return 0; } +#endif diff --git a/CPP/miscelanious/solveQuadraticEquation.h b/CPP/miscelanious/solveQuadraticEquation.h new file mode 100644 index 0000000..e0a0bdd --- /dev/null +++ b/CPP/miscelanious/solveQuadraticEquation.h @@ -0,0 +1,7 @@ +#pragma once +#include + +void print(const std::string s); +float getDelta(float a, float b, float c); +float calculateFirstTerm(float a, float b, float delta); +float calculateSecondTerm(float a, float b, float delta); diff --git a/CPP/miscelanious/test_challenges.cpp b/CPP/miscelanious/test_challenges.cpp new file mode 100644 index 0000000..688a9bc --- /dev/null +++ b/CPP/miscelanious/test_challenges.cpp @@ -0,0 +1,198 @@ +/* Tests for CPP/miscelanious: all testable functions from 4 source files */ + +#include +#include +#include +#include +#include +#include +#include + +/* ----------------------------------------------------------------------- + * Include headers (not source files) - compiled separately via Makefile + * ----------------------------------------------------------------------- */ +#include "howOftenDoesCharOccur.h" +#include "quickchallenges.h" +#include "reverseString.h" +#include "solveQuadraticEquation.h" + +/* ----------------------------------------------------------------------- + * Helper + * ----------------------------------------------------------------------- */ +static bool nearlyEqual(float a, float b, float eps = 1e-4f) { + return std::fabs(a - b) < eps; +} + +/* ----------------------------------------------------------------------- + * quickchallenges: sumStartEnd + * ----------------------------------------------------------------------- */ +static void test_sumStartEnd() { + assert(sumStartEnd(0, 1000) == 500500); + assert(sumStartEnd(1, 10) == 55); + assert(sumStartEnd(0, 0) == 0); + assert(sumStartEnd(5, 5) == 5); + assert(sumStartEnd(-3, 3) == 0); + assert(sumStartEnd(1, 1) == 1); +} + +/* ----------------------------------------------------------------------- + * solveQuadraticEquation: getDelta, calculateFirstTerm, calculateSecondTerm + * ----------------------------------------------------------------------- */ +static void test_getDelta() { + /* x^2 - 5x + 6 = 0: a=1, b=-5, c=6 => delta = 25 - 24 = 1 */ + assert(nearlyEqual(getDelta(1.0f, -5.0f, 6.0f), 1.0f)); + + /* x^2 + 2x + 1 = 0: delta = 4 - 4 = 0 */ + assert(nearlyEqual(getDelta(1.0f, 2.0f, 1.0f), 0.0f)); + + /* x^2 + x + 1 = 0: delta = 1 - 4 = -3 */ + assert(nearlyEqual(getDelta(1.0f, 1.0f, 1.0f), -3.0f)); + + /* 2x^2 - 4x + 0 = 0: delta = 16 - 0 = 16 */ + assert(nearlyEqual(getDelta(2.0f, -4.0f, 0.0f), 16.0f)); +} + +static void test_calculateFirstTerm() { + /* x^2 - 5x + 6 = 0: roots 2 and 3 */ + float delta = getDelta(1.0f, -5.0f, 6.0f); + float x1 = calculateFirstTerm(1.0f, -5.0f, delta); + float x2 = calculateSecondTerm(1.0f, -5.0f, delta); + assert(nearlyEqual(x1, 2.0f)); + assert(nearlyEqual(x2, 3.0f)); +} + +static void test_calculateSecondTerm() { + /* x^2 + 2x + 1 = 0: double root -1 */ + float delta = getDelta(1.0f, 2.0f, 1.0f); + float x1 = calculateFirstTerm(1.0f, 2.0f, delta); + float x2 = calculateSecondTerm(1.0f, 2.0f, delta); + assert(nearlyEqual(x1, -1.0f)); + assert(nearlyEqual(x2, -1.0f)); +} + +static void test_quadratic_large() { + /* 2x^2 - 4x = 0: roots 0 and 2 */ + float delta = getDelta(2.0f, -4.0f, 0.0f); + float x1 = calculateFirstTerm(2.0f, -4.0f, delta); + float x2 = calculateSecondTerm(2.0f, -4.0f, delta); + /* smaller root first */ + assert(nearlyEqual(x1, 0.0f)); + assert(nearlyEqual(x2, 2.0f)); +} + +/* ----------------------------------------------------------------------- + * reverseString: reverseStringManual + * ----------------------------------------------------------------------- */ +static void test_reverseStringManual() { + assert(reverseStringManual("hello") == "olleh"); + assert(reverseStringManual("abcde") == "edcba"); + assert(reverseStringManual("abcd") == "dcba"); + assert(reverseStringManual("a") == "a"); + assert(reverseStringManual("") == ""); + assert(reverseStringManual("ab") == "ba"); + assert(reverseStringManual("racecar") == "racecar"); +} + +/* ----------------------------------------------------------------------- + * howOftenDoesCharOccur: computeCharOccurences, printCharOccurenceVector + * ----------------------------------------------------------------------- */ +static void test_computeCharOccurences_basic() { + auto v = computeCharOccurences("aaaabbbcca"); + assert(v.size() == 4); + assert(v[0].c == 'a' && v[0].occurrence == 4); + assert(v[1].c == 'b' && v[1].occurrence == 3); + assert(v[2].c == 'c' && v[2].occurrence == 2); + assert(v[3].c == 'a' && v[3].occurrence == 1); +} + +static void test_computeCharOccurences_single() { + auto v = computeCharOccurences("x"); + assert(v.size() == 1); + assert(v[0].c == 'x' && v[0].occurrence == 1); +} + +static void test_computeCharOccurences_empty() { + auto v = computeCharOccurences(""); + assert(v.empty()); +} + +static void test_computeCharOccurences_all_same() { + auto v = computeCharOccurences("zzzz"); + assert(v.size() == 1); + assert(v[0].c == 'z' && v[0].occurrence == 4); +} + +static void test_computeCharOccurences_alternating() { + auto v = computeCharOccurences("ababab"); + assert(v.size() == 6); + for (auto &e : v) { + assert(e.occurrence == 1); + } +} + +static void test_printCharOccurenceVector_output() { + auto v = computeCharOccurences("aab"); + /* Capture stdout */ + std::streambuf *old = std::cout.rdbuf(); + std::ostringstream oss; + std::cout.rdbuf(oss.rdbuf()); + printCharOccurenceVector(v); + std::cout.rdbuf(old); + std::string out = oss.str(); + assert(out.find("\"a\"") != std::string::npos); + assert(out.find("\"b\"") != std::string::npos); + assert(out.find('[') != std::string::npos); + assert(out.find(']') != std::string::npos); +} + +static void test_printCharOccurenceVector_single() { + std::vector v; + charOccurence e; + e.c = 'x'; + e.occurrence = 3; + v.push_back(e); + std::streambuf *old = std::cout.rdbuf(); + std::ostringstream oss; + std::cout.rdbuf(oss.rdbuf()); + printCharOccurenceVector(v); + std::cout.rdbuf(old); + std::string out = oss.str(); + assert(out.find("\"x\"") != std::string::npos); + assert(out.find("3") != std::string::npos); +} + +static void test_print_function() { + /* print() is called inside main() - test it directly via output capture */ + std::streambuf *old = std::cout.rdbuf(); + std::ostringstream oss; + std::cout.rdbuf(oss.rdbuf()); + print("hello test"); + print("Enter quadratic equation constants: a, b, c as in: ax^2 + bx + c = 0"); + std::cout.rdbuf(old); + assert(oss.str().find("hello test") != std::string::npos); + assert(oss.str().find("Enter quadratic equation constants") != + std::string::npos); +} + +/* ----------------------------------------------------------------------- + * main + * ----------------------------------------------------------------------- */ +int main() { + test_sumStartEnd(); + test_getDelta(); + test_calculateFirstTerm(); + test_calculateSecondTerm(); + test_quadratic_large(); + test_reverseStringManual(); + test_computeCharOccurences_basic(); + test_computeCharOccurences_single(); + test_computeCharOccurences_empty(); + test_computeCharOccurences_all_same(); + test_computeCharOccurences_alternating(); + test_printCharOccurenceVector_output(); + test_printCharOccurenceVector_single(); + test_print_function(); + + std::cout << "All tests passed!\n"; + return 0; +} diff --git a/TS/battery-status/package-lock.json b/TS/battery-status/package-lock.json index e52a4f8..4769049 100644 --- a/TS/battery-status/package-lock.json +++ b/TS/battery-status/package-lock.json @@ -12,13 +12,25 @@ "react-dom": "^18.3.1" }, "devDependencies": { + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", "@types/react": "^18.3.5", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.1", + "@vitest/coverage-v8": "^4.1.4", + "jsdom": "^29.0.2", "typescript": "^5.5.4", - "vite": "^5.4.1" + "vite": "^5.4.1", + "vitest": "^4.1.4" } }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -33,6 +45,45 @@ "node": ">=6.0.0" } }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.1.10", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.10.tgz", + "integrity": "sha512-02OhhkKtgNRuicQ/nF3TRnGsxL9wp0r3Y7VlKWyOHHGmGyvXv03y+PnymU8FKFJMTjIr1Bk8U2g1HWSLrpAHww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.1.1", + "@csstools/css-color-parser": "^4.0.2", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.9.tgz", + "integrity": "sha512-r3ElRr7y8ucyN2KdICwGsmj19RoN13CLCa/pvGydghWK6ZzeKQ+TcDjVdtEZz2ElpndM5jXw//B9CEee0mWnVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -64,6 +115,7 @@ "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -186,9 +238,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -220,13 +272,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", - "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.2" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -267,6 +319,16 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", @@ -302,19 +364,195 @@ } }, "node_modules/@babel/types": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", - "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", + "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", + "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.1.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.2.tgz", + "integrity": "sha512-5GkLzz4prTIpoyeUiIu3iV6CSG3Plo7xRVOFPKI7FVEJ3mZ0A8SwK0XU3Gl7xAkiQ+mDyam+NNp875/C5y+jSA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -604,6 +842,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/netbsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", @@ -621,6 +876,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/openbsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", @@ -638,6 +910,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", @@ -706,6 +995,24 @@ "node": ">=12" } }, + "node_modules/@exodus/bytes": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -735,9 +1042,9 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.30", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", - "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -745,6 +1052,292 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz", + "integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", + "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", + "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", + "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.9.2", + "@emnapi/runtime": "1.9.2", + "@napi-rs/wasm-runtime": "^1.1.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -1032,6 +1625,107 @@ "win32" ] }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1077,6 +1771,24 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1097,6 +1809,7 @@ "integrity": "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -1108,6 +1821,7 @@ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -1133,6 +1847,196 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.4.tgz", + "integrity": "sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.4", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.4", + "vitest": "4.1.4" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.4.tgz", + "integrity": "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.4.tgz", + "integrity": "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.4.tgz", + "integrity": "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.4", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.4.tgz", + "integrity": "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.4", + "@vitest/utils": "4.1.4", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.4.tgz", + "integrity": "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.4.tgz", + "integrity": "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.4", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", + "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/browserslist": { "version": "4.25.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.3.tgz", @@ -1153,6 +2057,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001735", "electron-to-chromium": "^1.5.204", @@ -1187,6 +2092,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1194,6 +2109,27 @@ "dev": true, "license": "MIT" }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -1201,6 +2137,20 @@ "dev": true, "license": "MIT" }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -1219,6 +2169,40 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, "node_modules/electron-to-chromium": { "version": "1.5.208", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.208.tgz", @@ -1226,6 +2210,26 @@ "dev": true, "license": "ISC" }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -1275,6 +2279,44 @@ "node": ">=6" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1300,12 +2342,150 @@ "node": ">=6.9.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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "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, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "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==", "license": "MIT" }, + "node_modules/jsdom": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.2.tgz", + "integrity": "sha512-9VnGEBosc/ZpwyOsJBCQ/3I5p7Q5ngOY14a9bf5btenAORmZfDse1ZEheMiWcJ3h81+Fv7HmJFdS0szo/waF2w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@asamuzakjp/css-color": "^5.1.5", + "@asamuzakjp/dom-selector": "^7.0.6", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.1", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.7", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.24.5", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.3.tgz", + "integrity": "sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -1332,6 +2512,268 @@ "node": ">=6" } }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "peer": true, + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -1354,6 +2796,84 @@ "yallist": "^3.0.2" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "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, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -1387,6 +2907,37 @@ "dev": true, "license": "MIT" }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1394,10 +2945,24 @@ "dev": true, "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", + "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", "dev": true, "funding": [ { @@ -1423,11 +2988,37 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -1440,6 +3031,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -1448,6 +3040,13 @@ "react": "^18.3.1" } }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -1458,6 +3057,71 @@ "node": ">=0.10.0" } }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", + "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.124.0", + "@rolldown/pluginutils": "1.0.0-rc.15" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-x64": "1.0.0-rc.15", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" + } + }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz", + "integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==", + "dev": true, + "license": "MIT" + }, "node_modules/rollup": { "version": "4.48.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.48.1.tgz", @@ -1498,6 +3162,19 @@ "fsevents": "~2.3.2" } }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -1517,6 +3194,13 @@ "semver": "bin/semver.js" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -1527,6 +3211,151 @@ "node": ">=0.10.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", + "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.0.28", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.28.tgz", + "integrity": "sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.28" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.28", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.28.tgz", + "integrity": "sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, "node_modules/typescript": { "version": "5.9.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", @@ -1541,6 +3370,16 @@ "node": ">=14.17" } }, + "node_modules/undici": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.7.tgz", + "integrity": "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -1578,6 +3417,7 @@ "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -1632,6 +3472,675 @@ } } }, + "node_modules/vitest": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.4.tgz", + "integrity": "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.4", + "@vitest/mocker": "4.1.4", + "@vitest/pretty-format": "4.1.4", + "@vitest/runner": "4.1.4", + "@vitest/snapshot": "4.1.4", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.4", + "@vitest/browser-preview": "4.1.4", + "@vitest/browser-webdriverio": "4.1.4", + "@vitest/coverage-istanbul": "4.1.4", + "@vitest/coverage-v8": "4.1.4", + "@vitest/ui": "4.1.4", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz", + "integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/vite": { + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", + "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.15", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/TS/battery-status/package.json b/TS/battery-status/package.json index d3e5565..c91967d 100644 --- a/TS/battery-status/package.json +++ b/TS/battery-status/package.json @@ -7,17 +7,24 @@ "dev": "vite", "typecheck": "tsc --noEmit", "build": "vite build", - "preview": "vite preview --strictPort --port 5173" + "preview": "vite preview --strictPort --port 5173", + "test": "vitest run", + "test:coverage": "vitest run --coverage" }, "dependencies": { "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { - "@vitejs/plugin-react": "^4.3.1", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", "@types/react": "^18.3.5", "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "@vitest/coverage-v8": "^4.1.4", + "jsdom": "^29.0.2", "typescript": "^5.5.4", - "vite": "^5.4.1" + "vite": "^5.4.1", + "vitest": "^4.1.4" } } diff --git a/TS/battery-status/src/App.test.tsx b/TS/battery-status/src/App.test.tsx new file mode 100644 index 0000000..a9b7240 --- /dev/null +++ b/TS/battery-status/src/App.test.tsx @@ -0,0 +1,152 @@ +import { describe, it, expect, vi, afterEach } from 'vitest' +import { render, screen, cleanup } from '@testing-library/react' +import { App } from './App' + +// Mock the hooks +vi.mock('./useBattery') +vi.mock('./useBeforeUnload') + +import { useBattery } from './useBattery' +import { useBeforeUnload } from './useBeforeUnload' + +const mockUseBattery = vi.mocked(useBattery) +const mockUseBeforeUnload = vi.mocked(useBeforeUnload) + +describe('App', () => { + afterEach(() => { + cleanup() + vi.resetAllMocks() + }) + + it('shows unsupported message when not supported', () => { + mockUseBattery.mockReturnValue({ + supported: false, + loading: false, + error: null, + charging: false, + chargingTime: Infinity, + dischargingTime: Infinity, + level: 1, + }) + + render() + expect(screen.getByText('Battery Status')).toBeInTheDocument() + expect(screen.getByText(/not supported/)).toBeInTheDocument() + expect(mockUseBeforeUnload).toHaveBeenCalledWith(true, expect.any(String)) + }) + + it('shows loading state', () => { + mockUseBattery.mockReturnValue({ + supported: true, + loading: true, + error: null, + charging: false, + chargingTime: Infinity, + dischargingTime: Infinity, + level: 1, + }) + + render() + expect(screen.getByText('Loading…')).toBeInTheDocument() + }) + + it('shows error message', () => { + mockUseBattery.mockReturnValue({ + supported: true, + loading: false, + error: 'Something went wrong', + charging: false, + chargingTime: Infinity, + dischargingTime: Infinity, + level: 1, + }) + + render() + expect(screen.getByText('Something went wrong')).toBeInTheDocument() + }) + + it('shows battery info when charging', () => { + mockUseBattery.mockReturnValue({ + supported: true, + loading: false, + error: null, + charging: true, + chargingTime: 7200, + dischargingTime: Infinity, + level: 0.65, + }) + + render() + expect(screen.getByText('Yes')).toBeInTheDocument() + expect(screen.getByText('65%')).toBeInTheDocument() + expect(screen.getByText('Time to full:')).toBeInTheDocument() + expect(screen.getByText('2h 0m')).toBeInTheDocument() + // Time to empty should not be shown when charging + expect(screen.queryByText('Time to empty:')).not.toBeInTheDocument() + }) + + it('shows battery info when discharging', () => { + mockUseBattery.mockReturnValue({ + supported: true, + loading: false, + error: null, + charging: false, + chargingTime: Infinity, + dischargingTime: 5400, + level: 0.42, + }) + + render() + expect(screen.getByText('No')).toBeInTheDocument() + expect(screen.getByText('42%')).toBeInTheDocument() + expect(screen.getByText('Time to empty:')).toBeInTheDocument() + expect(screen.getByText('1h 30m')).toBeInTheDocument() + expect(screen.queryByText('Time to full:')).not.toBeInTheDocument() + }) + + it('handles short times (minutes only)', () => { + mockUseBattery.mockReturnValue({ + supported: true, + loading: false, + error: null, + charging: false, + chargingTime: Infinity, + dischargingTime: 300, + level: 0.1, + }) + + render() + expect(screen.getByText('5m')).toBeInTheDocument() + }) + + it('handles infinite discharging time as N/A', () => { + mockUseBattery.mockReturnValue({ + supported: true, + loading: false, + error: null, + charging: false, + chargingTime: Infinity, + dischargingTime: Infinity, + level: 0.5, + }) + + render() + // Infinity is a number so it would try to show, but formatTime returns N/A + expect(screen.getByText('N/A')).toBeInTheDocument() + }) + + it('handles negative time as N/A', () => { + mockUseBattery.mockReturnValue({ + supported: true, + loading: false, + error: null, + charging: true, + chargingTime: -1, + dischargingTime: Infinity, + level: 0.5, + }) + + render() + expect(screen.getByText('N/A')).toBeInTheDocument() + }) +}) diff --git a/TS/battery-status/src/test-setup.ts b/TS/battery-status/src/test-setup.ts new file mode 100644 index 0000000..a9d0dd3 --- /dev/null +++ b/TS/battery-status/src/test-setup.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom/vitest' diff --git a/TS/battery-status/src/useBattery.test.ts b/TS/battery-status/src/useBattery.test.ts new file mode 100644 index 0000000..1ab3a06 --- /dev/null +++ b/TS/battery-status/src/useBattery.test.ts @@ -0,0 +1,215 @@ +import { describe, it, expect, vi, afterEach } from 'vitest' +import { renderHook, act, cleanup } from '@testing-library/react' +import { useBattery } from './useBattery' + +type BatteryManagerLike = { + charging: boolean + chargingTime: number + dischargingTime: number + level: number + addEventListener?: (type: string, listener: () => void) => void + removeEventListener?: (type: string, listener: () => void) => void + onchargingchange?: (() => void) | null + onlevelchange?: (() => void) | null + onchargingtimechange?: (() => void) | null + ondischargingtimechange?: (() => void) | null +} + +function createMockBattery(overrides: Partial = {}): BatteryManagerLike { + return { + charging: true, + chargingTime: 3600, + dischargingTime: Infinity, + level: 0.75, + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + ...overrides, + } +} + +describe('useBattery', () => { + const originalGetBattery = navigator.getBattery + + afterEach(() => { + cleanup() + Object.defineProperty(navigator, 'getBattery', { + value: originalGetBattery, + configurable: true, + writable: true, + }) + }) + + it('returns supported=false when getBattery is not available', async () => { + Object.defineProperty(navigator, 'getBattery', { + value: undefined, + configurable: true, + writable: true, + }) + + const { result } = renderHook(() => useBattery()) + // Wait for the async init to complete + await vi.waitFor(() => { + expect(result.current.loading).toBe(false) + }) + expect(result.current.supported).toBe(false) + }) + + it('returns battery state when getBattery resolves', async () => { + const mockBattery = createMockBattery() + Object.defineProperty(navigator, 'getBattery', { + value: () => Promise.resolve(mockBattery), + configurable: true, + writable: true, + }) + + const { result } = renderHook(() => useBattery()) + await vi.waitFor(() => { + expect(result.current.loading).toBe(false) + }) + + expect(result.current.supported).toBe(true) + expect(result.current.charging).toBe(true) + expect(result.current.level).toBe(0.75) + expect(result.current.chargingTime).toBe(3600) + expect(result.current.error).toBeNull() + }) + + it('subscribes to battery events and cleans up on unmount', async () => { + const mockBattery = createMockBattery() + Object.defineProperty(navigator, 'getBattery', { + value: () => Promise.resolve(mockBattery), + configurable: true, + writable: true, + }) + + const { result, unmount } = renderHook(() => useBattery()) + await vi.waitFor(() => { + expect(result.current.loading).toBe(false) + }) + + expect(mockBattery.addEventListener).toHaveBeenCalledWith('chargingchange', expect.any(Function)) + expect(mockBattery.addEventListener).toHaveBeenCalledWith('levelchange', expect.any(Function)) + expect(mockBattery.addEventListener).toHaveBeenCalledWith('chargingtimechange', expect.any(Function)) + expect(mockBattery.addEventListener).toHaveBeenCalledWith('dischargingtimechange', expect.any(Function)) + + unmount() + + expect(mockBattery.removeEventListener).toHaveBeenCalledWith('chargingchange', expect.any(Function)) + expect(mockBattery.removeEventListener).toHaveBeenCalledWith('levelchange', expect.any(Function)) + }) + + it('uses fallback on* properties when addEventListener is missing', async () => { + const mockBattery = createMockBattery({ + addEventListener: undefined, + removeEventListener: undefined, + onlevelchange: null, + onchargingchange: null, + onchargingtimechange: null, + ondischargingtimechange: null, + }) + + Object.defineProperty(navigator, 'getBattery', { + value: () => Promise.resolve(mockBattery), + configurable: true, + writable: true, + }) + + const { result, unmount } = renderHook(() => useBattery()) + await vi.waitFor(() => { + expect(result.current.loading).toBe(false) + }) + + expect(mockBattery.onchargingchange).toBeTypeOf('function') + expect(mockBattery.onlevelchange).toBeTypeOf('function') + expect(mockBattery.onchargingtimechange).toBeTypeOf('function') + expect(mockBattery.ondischargingtimechange).toBeTypeOf('function') + + // Simulate change via fallback property + mockBattery.level = 0.5 + act(() => { + mockBattery.onlevelchange!() + }) + expect(result.current.level).toBe(0.5) + + // Unmount clears the callbacks + unmount() + expect(mockBattery.onchargingchange).toBeNull() + expect(mockBattery.onlevelchange).toBeNull() + }) + + it('updates state when battery events fire', async () => { + const listeners = new Map void>() + const mockBattery = createMockBattery({ + addEventListener: vi.fn((type: string, listener: () => void) => { + listeners.set(type, listener) + }), + removeEventListener: vi.fn(), + }) + + Object.defineProperty(navigator, 'getBattery', { + value: () => Promise.resolve(mockBattery), + configurable: true, + writable: true, + }) + + const { result } = renderHook(() => useBattery()) + await vi.waitFor(() => { + expect(result.current.loading).toBe(false) + }) + + // Simulate a level change + mockBattery.level = 0.5 + mockBattery.charging = false + act(() => { + listeners.get('levelchange')!() + }) + + expect(result.current.level).toBe(0.5) + expect(result.current.charging).toBe(false) + }) + + it('handles getBattery rejection with error message', async () => { + Object.defineProperty(navigator, 'getBattery', { + value: () => Promise.reject(new Error('Permission denied')), + configurable: true, + writable: true, + }) + + const { result } = renderHook(() => useBattery()) + await vi.waitFor(() => { + expect(result.current.loading).toBe(false) + }) + + expect(result.current.error).toBe('Permission denied') + }) + + it('handles getBattery rejection with non-Error thrown', async () => { + Object.defineProperty(navigator, 'getBattery', { + value: () => Promise.reject('string error'), + configurable: true, + writable: true, + }) + + const { result } = renderHook(() => useBattery()) + await vi.waitFor(() => { + expect(result.current.loading).toBe(false) + }) + + expect(result.current.error).toBe('Failed to read battery status') + }) + + it('handles getBattery resolving to null', async () => { + Object.defineProperty(navigator, 'getBattery', { + value: () => Promise.resolve(null), + configurable: true, + writable: true, + }) + + const { result } = renderHook(() => useBattery()) + // The hook would return early since battery is null, staying in loading state + // Wait a tick to let the async init() run + await new Promise(r => setTimeout(r, 50)) + // Since early return doesn't call setLoading(false), it stays loading + expect(result.current.loading).toBe(true) + }) +}) diff --git a/TS/battery-status/src/useBattery.ts b/TS/battery-status/src/useBattery.ts index 77c2f74..5a22f35 100644 --- a/TS/battery-status/src/useBattery.ts +++ b/TS/battery-status/src/useBattery.ts @@ -72,13 +72,11 @@ export function useBattery() { battery?.removeEventListener?.('levelchange', onChange) battery?.removeEventListener?.('chargingtimechange', onChange) battery?.removeEventListener?.('dischargingtimechange', onChange) - if (!battery?.removeEventListener) { - if (battery) { - battery.onchargingchange = null - battery.onlevelchange = null - battery.onchargingtimechange = null - battery.ondischargingtimechange = null - } + if (battery && !battery.removeEventListener) { + battery.onchargingchange = null + battery.onlevelchange = null + battery.onchargingtimechange = null + battery.ondischargingtimechange = null } } diff --git a/TS/battery-status/src/useBeforeUnload.test.ts b/TS/battery-status/src/useBeforeUnload.test.ts new file mode 100644 index 0000000..d133caa --- /dev/null +++ b/TS/battery-status/src/useBeforeUnload.test.ts @@ -0,0 +1,57 @@ +import { describe, it, expect } from 'vitest' +import { useBeforeUnload } from './useBeforeUnload' +import { renderHook, cleanup } from '@testing-library/react' + +describe('useBeforeUnload', () => { + afterEach(() => { + cleanup() + }) + + it('registers beforeunload handler when enabled', () => { + const addSpy = vi.spyOn(window, 'addEventListener') + renderHook(() => useBeforeUnload(true, 'Leave?')) + expect(addSpy).toHaveBeenCalledWith('beforeunload', expect.any(Function)) + addSpy.mockRestore() + }) + + it('does not register handler when disabled', () => { + const addSpy = vi.spyOn(window, 'addEventListener') + renderHook(() => useBeforeUnload(false, 'Leave?')) + expect(addSpy).not.toHaveBeenCalledWith('beforeunload', expect.any(Function)) + addSpy.mockRestore() + }) + + it('removes handler on unmount', () => { + const removeSpy = vi.spyOn(window, 'removeEventListener') + const { unmount } = renderHook(() => useBeforeUnload(true, 'Leave?')) + unmount() + expect(removeSpy).toHaveBeenCalledWith('beforeunload', expect.any(Function)) + removeSpy.mockRestore() + }) + + it('handler sets returnValue and prevents default', () => { + let captured: ((e: BeforeUnloadEvent) => void) | undefined + const addSpy = vi.spyOn(window, 'addEventListener').mockImplementation((type, handler) => { + if (type === 'beforeunload') captured = handler as (e: BeforeUnloadEvent) => void + }) + + renderHook(() => useBeforeUnload(true, 'Stay here')) + + expect(captured).toBeDefined() + const event = new Event('beforeunload') as BeforeUnloadEvent + const preventSpy = vi.spyOn(event, 'preventDefault') + captured!(event) + expect(preventSpy).toHaveBeenCalled() + // jsdom may coerce returnValue to boolean; just verify it was set + expect(event.returnValue).toBeDefined() + + addSpy.mockRestore() + }) + + it('uses default values when called with no arguments', () => { + const addSpy = vi.spyOn(window, 'addEventListener') + renderHook(() => useBeforeUnload()) + expect(addSpy).toHaveBeenCalledWith('beforeunload', expect.any(Function)) + addSpy.mockRestore() + }) +}) diff --git a/TS/battery-status/tsconfig.base.json b/TS/battery-status/tsconfig.base.json index cf37361..110eeeb 100644 --- a/TS/battery-status/tsconfig.base.json +++ b/TS/battery-status/tsconfig.base.json @@ -14,6 +14,6 @@ "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "types": [] + "types": ["vitest/globals"] } } diff --git a/TS/battery-status/vite.config.ts b/TS/battery-status/vite.config.ts index fbc070b..04afdfb 100644 --- a/TS/battery-status/vite.config.ts +++ b/TS/battery-status/vite.config.ts @@ -6,5 +6,21 @@ export default defineConfig({ server: { port: 5173, open: false + }, + test: { + globals: true, + environment: 'jsdom', + setupFiles: ['./src/test-setup.ts'], + coverage: { + provider: 'v8', + include: ['src/**/*.{ts,tsx}'], + exclude: ['src/main.tsx', 'src/test-setup.ts', 'src/**/*.test.{ts,tsx}'], + thresholds: { + statements: 100, + branches: 100, + functions: 100, + lines: 100 + } + } } }) diff --git a/TS/champions_leauge_scores/package-lock.json b/TS/champions_leauge_scores/package-lock.json index a086e83..90ed88f 100644 --- a/TS/champions_leauge_scores/package-lock.json +++ b/TS/champions_leauge_scores/package-lock.json @@ -16,19 +16,33 @@ "react-dom": "^18.3.1" }, "devDependencies": { + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/node": "^20.12.12", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", + "@types/supertest": "^7.2.0", "@vitejs/plugin-react": "^4.3.1", + "@vitest/coverage-v8": "^4.1.4", "concurrently": "^8.2.2", + "jsdom": "^29.0.2", + "supertest": "^7.2.2", "ts-node-dev": "^2.0.0", "tsx": "^4.19.2", "typescript": "^5.4.5", - "vite": "^5.3.3" + "vite": "^5.3.3", + "vitest": "^4.1.4" } }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -54,6 +68,45 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.1.10", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.10.tgz", + "integrity": "sha512-02OhhkKtgNRuicQ/nF3TRnGsxL9wp0r3Y7VlKWyOHHGmGyvXv03y+PnymU8FKFJMTjIr1Bk8U2g1HWSLrpAHww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.1.1", + "@csstools/css-color-parser": "^4.0.2", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.9.tgz", + "integrity": "sha512-r3ElRr7y8ucyN2KdICwGsmj19RoN13CLCa/pvGydghWK6ZzeKQ+TcDjVdtEZz2ElpndM5jXw//B9CEee0mWnVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -85,6 +138,7 @@ "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -243,9 +297,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -277,13 +331,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", - "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.2" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -394,19 +448,42 @@ "license": "MIT" }, "node_modules/@babel/types": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", - "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -420,6 +497,159 @@ "node": ">=12" } }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", + "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", + "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.1.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.2.tgz", + "integrity": "sha512-5GkLzz4prTIpoyeUiIu3iV6CSG3Plo7xRVOFPKI7FVEJ3mZ0A8SwK0XU3Gl7xAkiQ+mDyam+NNp875/C5y+jSA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -862,6 +1092,24 @@ "node": ">=12" } }, + "node_modules/@exodus/bytes": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -912,6 +1160,316 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz", + "integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", + "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", + "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", + "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.9.2", + "@emnapi/runtime": "1.9.2", + "@napi-rs/wasm-runtime": "^1.1.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -1199,6 +1757,89 @@ "win32" ] }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -1227,6 +1868,24 @@ "dev": true, "license": "MIT" }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1283,6 +1942,17 @@ "@types/node": "*" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -1293,6 +1963,13 @@ "@types/node": "*" } }, + "node_modules/@types/cookiejar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/cors": { "version": "2.8.19", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", @@ -1303,6 +1980,13 @@ "@types/node": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1343,6 +2027,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -1356,6 +2047,7 @@ "integrity": "sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -1387,6 +2079,7 @@ "integrity": "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -1398,6 +2091,7 @@ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -1439,6 +2133,30 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/superagent": { + "version": "8.1.9", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", + "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/supertest": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-7.2.0.tgz", + "integrity": "sha512-uh2Lv57xvggst6lCqNdFAmDSvoMG7M/HDtX4iUCquxQ5EGPtaPM5PL5Hmi7LCvOG8db7YaCPNJEeoI8s/WzIQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" + } + }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", @@ -1460,6 +2178,124 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.4.tgz", + "integrity": "sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.4", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.4", + "vitest": "4.1.4" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.4.tgz", + "integrity": "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.4.tgz", + "integrity": "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.4.tgz", + "integrity": "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.4", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.4.tgz", + "integrity": "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.4", + "@vitest/utils": "4.1.4", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.4.tgz", + "integrity": "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.4.tgz", + "integrity": "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.4", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -1546,12 +2382,69 @@ "dev": true, "license": "MIT" }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, "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==", "license": "MIT" }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", + "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1576,6 +2469,16 @@ "dev": true, "license": "MIT" }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1657,6 +2560,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001735", "electron-to-chromium": "^1.5.204", @@ -1736,6 +2640,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1838,6 +2752,16 @@ "node": ">= 0.8" } }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1916,6 +2840,13 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true, + "license": "MIT" + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -1936,6 +2867,27 @@ "dev": true, "license": "MIT" }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -1943,6 +2895,20 @@ "dev": true, "license": "MIT" }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", @@ -1969,6 +2935,13 @@ "ms": "2.0.0" } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1987,6 +2960,16 @@ "node": ">= 0.8" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -1997,6 +2980,27 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -2007,6 +3011,13 @@ "node": ">=0.3.1" } }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -2072,6 +3083,19 @@ "node": ">= 0.8" } }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=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", @@ -2090,6 +3114,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -2172,6 +3203,16 @@ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -2181,6 +3222,16 @@ "node": ">= 0.6" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/express": { "version": "4.21.2", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", @@ -2227,6 +3278,13 @@ "url": "https://opencollective.com/express" } }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true, + "license": "MIT" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -2279,9 +3337,9 @@ } }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -2294,6 +3352,24 @@ "node": ">= 6" } }, + "node_modules/formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -2509,6 +3585,26 @@ "node": ">= 0.4" } }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "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, + "license": "MIT" + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -2537,6 +3633,16 @@ "node": ">=0.10.0" } }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2636,12 +3742,123 @@ "node": ">=0.12.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "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, + "license": "BSD-3-Clause", + "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/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, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "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==", "license": "MIT" }, + "node_modules/jsdom": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.2.tgz", + "integrity": "sha512-9VnGEBosc/ZpwyOsJBCQ/3I5p7Q5ngOY14a9bf5btenAORmZfDse1ZEheMiWcJ3h81+Fv7HmJFdS0szo/waF2w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@asamuzakjp/css-color": "^5.1.5", + "@asamuzakjp/dom-selector": "^7.0.6", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.1", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.7", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.24.5", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.3.tgz", + "integrity": "sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -2668,6 +3885,268 @@ "node": ">=6" } }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "peer": true, + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -2697,6 +4176,67 @@ "yallist": "^3.0.2" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "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, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -2713,6 +4253,13 @@ "node": ">= 0.4" } }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -2773,6 +4320,16 @@ "node": ">= 0.6" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2881,6 +4438,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -2903,6 +4471,19 @@ "wrappy": "1" } }, + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.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", @@ -2935,6 +4516,13 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -2956,9 +4544,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", + "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", "dev": true, "funding": [ { @@ -2984,6 +4572,34 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.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, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -3003,6 +4619,16 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "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, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -3047,6 +4673,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -3059,6 +4686,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -3067,6 +4695,13 @@ "react": "^18.3.1" } }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -3090,6 +4725,20 @@ "node": ">=8.10.0" } }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -3100,6 +4749,16 @@ "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, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -3145,6 +4804,47 @@ "rimraf": "bin.js" } }, + "node_modules/rolldown": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", + "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.124.0", + "@rolldown/pluginutils": "1.0.0-rc.15" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-x64": "1.0.0-rc.15", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" + } + }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz", + "integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==", + "dev": true, + "license": "MIT" + }, "node_modules/rollup": { "version": "4.48.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.48.0.tgz", @@ -3221,6 +4921,19 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -3385,6 +5098,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -3422,6 +5142,13 @@ "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", "dev": true }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -3431,6 +5158,13 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", + "dev": true, + "license": "MIT" + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -3469,6 +5203,19 @@ "node": ">=4" } }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -3479,6 +5226,106 @@ "node": ">=0.10.0" } }, + "node_modules/superagent": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz", + "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "component-emitter": "^1.3.1", + "cookiejar": "^2.1.4", + "debug": "^4.3.7", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.5", + "formidable": "^3.5.4", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.14.1" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/superagent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/superagent/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, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/superagent/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, + "license": "MIT" + }, + "node_modules/superagent/node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/supertest": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz", + "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cookie-signature": "^1.2.2", + "methods": "^1.1.2", + "superagent": "^10.3.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/supertest/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -3508,6 +5355,109 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", + "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.0.28", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.28.tgz", + "integrity": "sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.28" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.28", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.28.tgz", + "integrity": "sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3530,6 +5480,32 @@ "node": ">=0.6" } }, + "node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -4111,6 +6087,7 @@ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4119,6 +6096,16 @@ "node": ">=14.17" } }, + "node_modules/undici": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.7.tgz", + "integrity": "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -4197,6 +6184,7 @@ "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -4251,6 +6239,722 @@ } } }, + "node_modules/vitest": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.4.tgz", + "integrity": "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.4", + "@vitest/mocker": "4.1.4", + "@vitest/pretty-format": "4.1.4", + "@vitest/runner": "4.1.4", + "@vitest/snapshot": "4.1.4", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.4", + "@vitest/browser-preview": "4.1.4", + "@vitest/browser-webdriverio": "4.1.4", + "@vitest/coverage-istanbul": "4.1.4", + "@vitest/coverage-v8": "4.1.4", + "@vitest/ui": "4.1.4", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz", + "integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest/node_modules/vite": { + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", + "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.15", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -4276,6 +6980,23 @@ "dev": true, "license": "ISC" }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/TS/champions_leauge_scores/package.json b/TS/champions_leauge_scores/package.json index 58e4a7f..6684890 100644 --- a/TS/champions_leauge_scores/package.json +++ b/TS/champions_leauge_scores/package.json @@ -7,27 +7,36 @@ "dev": "concurrently \"vite\" \"npm:server:dev\"", "build": "vite build", "preview": "vite preview", - "server:dev": "tsx watch server/src/server.ts" + "server:dev": "tsx watch server/src/main.ts", + "test": "vitest run", + "test:coverage": "vitest run --coverage" }, "dependencies": { "axios": "^1.7.2", - "dotenv": "^16.4.5", "cors": "^2.8.5", + "dotenv": "^16.4.5", "express": "^4.19.2", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { - "@vitejs/plugin-react": "^4.3.1", - "@types/express": "^4.17.21", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", "@types/node": "^20.12.12", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", + "@types/supertest": "^7.2.0", + "@vitejs/plugin-react": "^4.3.1", + "@vitest/coverage-v8": "^4.1.4", "concurrently": "^8.2.2", + "jsdom": "^29.0.2", + "supertest": "^7.2.2", "ts-node-dev": "^2.0.0", "tsx": "^4.19.2", "typescript": "^5.4.5", - "vite": "^5.3.3" + "vite": "^5.3.3", + "vitest": "^4.1.4" } } diff --git a/TS/champions_leauge_scores/server/src/main.ts b/TS/champions_leauge_scores/server/src/main.ts new file mode 100644 index 0000000..c373325 --- /dev/null +++ b/TS/champions_leauge_scores/server/src/main.ts @@ -0,0 +1,7 @@ +import { app } from './server.js'; + +const PORT = Number(process.env.PORT || 8787); + +app.listen(PORT, () => { + console.log(`[server] Listening on http://localhost:${PORT}`); +}); diff --git a/TS/champions_leauge_scores/server/src/server.test.ts b/TS/champions_leauge_scores/server/src/server.test.ts new file mode 100644 index 0000000..3545854 --- /dev/null +++ b/TS/champions_leauge_scores/server/src/server.test.ts @@ -0,0 +1,528 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import request from 'supertest'; +import type { Request, Response } from 'express'; + +vi.mock('axios', () => { + const interceptors = { + request: { use: vi.fn() }, + response: { use: vi.fn() }, + }; + return { + default: { + get: vi.fn(), + interceptors, + }, + }; +}); + +vi.mock('dotenv', () => ({ default: { config: vi.fn() } })); + +describe('server', () => { + let app: typeof import('./server').app; + let normalizeMatch: typeof import('./server').normalizeMatch; + let buildHeaders: typeof import('./server').buildHeaders; + let clipStr: typeof import('./server').clipStr; + let axiosRequestOnFulfilled: typeof import('./server').axiosRequestOnFulfilled; + let axiosRequestOnRejected: typeof import('./server').axiosRequestOnRejected; + let axiosResponseOnFulfilled: typeof import('./server').axiosResponseOnFulfilled; + let axiosResponseOnRejected: typeof import('./server').axiosResponseOnRejected; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let axiosMock: any; + + beforeEach(async () => { + vi.resetModules(); + delete process.env.FOOTBALL_DATA_API_KEY; + axiosMock = (await import('axios')).default; + const server = await import('./server'); + app = server.app; + normalizeMatch = server.normalizeMatch; + buildHeaders = server.buildHeaders; + clipStr = server.clipStr; + axiosRequestOnFulfilled = server.axiosRequestOnFulfilled; + axiosRequestOnRejected = server.axiosRequestOnRejected; + axiosResponseOnFulfilled = server.axiosResponseOnFulfilled; + axiosResponseOnRejected = server.axiosResponseOnRejected; + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe('clipStr', () => { + it('returns short strings unchanged', () => { + expect(clipStr('hello', 10)).toBe('hello'); + }); + + it('clips long strings', () => { + const long = 'a'.repeat(100); + const result = clipStr(long, 10); + expect(result).toBe('a'.repeat(10) + '…(+90)'); + }); + + it('returns empty string as-is', () => { + expect(clipStr('', 10)).toBe(''); + }); + }); + + describe('axiosRequestOnFulfilled', () => { + it('sets metadata.start and returns config', () => { + const config = { method: 'get', url: '/test' }; + const result = axiosRequestOnFulfilled(config); + expect(result).toBe(config); + expect(config).toHaveProperty('metadata'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect((config as any).metadata.start).toBeTypeOf('number'); + }); + + it('defaults method to GET in log', () => { + const config = { url: '/test' }; + const spy = vi.spyOn(console, 'log').mockImplementation(() => {}); + axiosRequestOnFulfilled(config); + expect(spy).toHaveBeenCalledWith(expect.stringContaining('[axios ->] GET /test')); + spy.mockRestore(); + }); + }); + + describe('axiosRequestOnRejected', () => { + it('rejects with the error', async () => { + const err = { message: 'bad request' }; + await expect(axiosRequestOnRejected(err)).rejects.toBe(err); + }); + + it('logs error without message', async () => { + const spy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await expect(axiosRequestOnRejected({} as any)).rejects.toEqual({}); + expect(spy).toHaveBeenCalledWith('[axios req error]', {}); + spy.mockRestore(); + }); + }); + + describe('axiosResponseOnFulfilled', () => { + it('returns the response and logs', () => { + const spy = vi.spyOn(console, 'log').mockImplementation(() => {}); + const response = { + status: 200, + config: { method: 'get', url: '/api/test', metadata: { start: Date.now() - 100 } }, + data: { key: 'value' }, + }; + const result = axiosResponseOnFulfilled(response); + expect(result).toBe(response); + expect(spy).toHaveBeenCalledWith(expect.stringContaining('[axios <-] 200 GET /api/test')); + spy.mockRestore(); + }); + + it('handles string data', () => { + const spy = vi.spyOn(console, 'log').mockImplementation(() => {}); + axiosResponseOnFulfilled({ + status: 200, + config: { method: 'post', url: '/test' }, + data: 'plain text', + }); + expect(spy).toHaveBeenCalledWith(expect.stringContaining('data=plain text')); + spy.mockRestore(); + }); + + it('clips large data', () => { + const spy = vi.spyOn(console, 'log').mockImplementation(() => {}); + axiosResponseOnFulfilled({ + status: 200, + config: { method: 'get', url: '/test' }, + data: 'x'.repeat(3000), + }); + expect(spy).toHaveBeenCalledWith(expect.stringContaining('…(+1000)')); + spy.mockRestore(); + }); + + it('handles non-serializable data', () => { + const spy = vi.spyOn(console, 'log').mockImplementation(() => {}); + const circular = {} as Record; + circular.self = circular; + axiosResponseOnFulfilled({ + status: 200, + config: { method: 'get', url: '/test' }, + data: circular, + }); + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('defaults method to GET when missing', () => { + const spy = vi.spyOn(console, 'log').mockImplementation(() => {}); + axiosResponseOnFulfilled({ + status: 200, + config: { url: '/test' }, + data: '', + }); + expect(spy).toHaveBeenCalledWith(expect.stringContaining('GET')); + spy.mockRestore(); + }); + }); + + describe('axiosResponseOnRejected', () => { + it('rejects and logs error with response status', async () => { + const spy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const err = { + config: { method: 'get', url: '/fail', metadata: { start: Date.now() } }, + response: { status: 500, data: { msg: 'error' } }, + message: 'AxiosError', + }; + await expect(axiosResponseOnRejected(err)).rejects.toBe(err); + expect(spy).toHaveBeenCalledWith(expect.stringContaining('[axios ! ] 500')); + spy.mockRestore(); + }); + + it('logs ERR when no response status', async () => { + const spy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const err = { message: 'Network Error' }; + await expect(axiosResponseOnRejected(err)).rejects.toBe(err); + expect(spy).toHaveBeenCalledWith(expect.stringContaining('ERR')); + spy.mockRestore(); + }); + + it('handles string response data', async () => { + const spy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const err = { + config: { method: 'get', url: '/fail' }, + response: { status: 429, data: 'Rate limited' }, + }; + await expect(axiosResponseOnRejected(err)).rejects.toBe(err); + expect(spy).toHaveBeenCalledWith(expect.stringContaining('data=Rate limited')); + spy.mockRestore(); + }); + + it('uses error message when no data', async () => { + const spy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const err = { config: {}, message: 'timeout' }; + await expect(axiosResponseOnRejected(err)).rejects.toBe(err); + expect(spy).toHaveBeenCalledWith(expect.stringContaining('data=timeout')); + spy.mockRestore(); + }); + + it('clips large response data', async () => { + const spy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const err = { + config: {}, + response: { status: 400, data: 'y'.repeat(3000) }, + }; + await expect(axiosResponseOnRejected(err)).rejects.toBe(err); + expect(spy).toHaveBeenCalledWith(expect.stringContaining('…(+1000)')); + spy.mockRestore(); + }); + + it('falls back to "error" when no message and no data', async () => { + const spy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + await expect(axiosResponseOnRejected({})).rejects.toEqual({}); + expect(spy).toHaveBeenCalledWith(expect.stringContaining('data=error')); + spy.mockRestore(); + }); + + it('handles non-serializable error response data', async () => { + const spy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const circular: Record = {}; + circular.self = circular; + const err = { + config: { method: 'get', url: '/fail' }, + response: { status: 500, data: circular }, + message: 'Circular data error', + }; + await expect(axiosResponseOnRejected(err)).rejects.toBe(err); + expect(spy).toHaveBeenCalledWith(expect.stringContaining('data=Circular data error')); + spy.mockRestore(); + }); + }); + + describe('logging middleware', () => { + it('logs request with query parameters', async () => { + const spy = vi.spyOn(console, 'log').mockImplementation(() => {}); + await request(app).get('/health?foo=bar'); + expect(spy.mock.calls.some(c => typeof c[0] === 'string' && c[0].includes('query='))).toBe(true); + spy.mockRestore(); + }); + + it('captures res.send body in log', async () => { + const spy = vi.spyOn(console, 'log').mockImplementation(() => {}); + await request(app).get('/health'); + const logLines = spy.mock.calls.map(c => c[0]).filter(s => typeof s === 'string'); + expect(logLines.some(l => l.includes('body='))).toBe(true); + spy.mockRestore(); + }); + + it('logs without body when response uses res.end() directly', async () => { + const spy = vi.spyOn(console, 'log').mockImplementation(() => {}); + // Register a route that bypasses json/send — bodyForLog stays undefined + app.get('/_test_no_body', (_req: Request, res: Response) => { + res.status(204).end(); + }); + await request(app).get('/_test_no_body'); + const responseLine = spy.mock.calls + .map(c => c[0]) + .filter(s => typeof s === 'string') + .find(l => l.includes('<-') && l.includes('/_test_no_body')); + expect(responseLine).toBeDefined(); + // No body= in log since bodyForLog was undefined + expect(responseLine).not.toContain('body='); + spy.mockRestore(); + }); + + it('handles non-serializable bodyForLog in finish handler', async () => { + const spy = vi.spyOn(console, 'log').mockImplementation(() => {}); + const circular: Record = {}; + circular.self = circular; + // Register a route that manually sets a circular object as bodyForLog + app.get('/_test_circular', (_req: Request, res: Response) => { + res.locals.bodyForLog = circular; + res.status(200).end(); + }); + await request(app).get('/_test_circular'); + // Should not throw — the catch in the finish handler absorbs the error + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + }); + + describe('GET /health', () => { + it('returns { ok: true }', async () => { + const res = await request(app).get('/health'); + expect(res.status).toBe(200); + expect(res.body).toEqual({ ok: true }); + }); + }); + + describe('buildHeaders', () => { + it('returns auth header with empty token when no API key', () => { + const headers = buildHeaders(); + expect(headers['X-Auth-Token']).toBe(''); + }); + }); + + describe('normalizeMatch', () => { + it('normalizes a full match object', () => { + const raw = { + id: 1, utcDate: '2024-01-01T00:00:00Z', status: 'FINISHED', + stage: 'GROUP_STAGE', group: 'Group A', matchday: 1, + homeTeam: { name: 'Team A' }, awayTeam: { name: 'Team B' }, + score: { fullTime: { home: 2, away: 1 } }, + competition: { name: 'UEFA Champions League' }, + venue: 'Stadium X', + referees: [{ name: 'Ref One' }, { name: 'Ref Two' }], + }; + const result = normalizeMatch(raw); + expect(result).toEqual({ + id: 1, utcDate: '2024-01-01T00:00:00Z', status: 'FINISHED', + stage: 'GROUP_STAGE', group: 'Group A', matchday: 1, + homeTeam: 'Team A', awayTeam: 'Team B', + score: { fullTime: { home: 2, away: 1 } }, + competition: 'UEFA Champions League', venue: 'Stadium X', + referees: ['Ref One', 'Ref Two'], + }); + }); + + it('handles null referees', () => { + const result = normalizeMatch({ + id: 2, utcDate: '', status: 'TIMED', + homeTeam: { name: 'A' }, awayTeam: { name: 'B' }, + score: {}, referees: null, + }); + expect(result.referees).toEqual([]); + }); + + it('handles undefined referees', () => { + const result = normalizeMatch({ + id: 3, utcDate: '', status: 'TIMED', + homeTeam: { name: 'A' }, awayTeam: { name: 'B' }, score: {}, + }); + expect(result.referees).toEqual([]); + }); + + it('filters out referees with falsy names', () => { + const result = normalizeMatch({ + id: 4, utcDate: '', status: 'TIMED', + homeTeam: { name: 'A' }, awayTeam: { name: 'B' }, score: {}, + referees: [{ name: 'Good Ref' }, { name: '' }, { name: null }, {}], + }); + expect(result.referees).toEqual(['Good Ref']); + }); + + it('defaults competition name when missing', () => { + const result = normalizeMatch({ + id: 5, utcDate: '', status: 'TIMED', + homeTeam: { name: 'A' }, awayTeam: { name: 'B' }, score: {}, + }); + expect(result.competition).toBe('UEFA Champions League'); + }); + + it('uses competition name when present', () => { + const result = normalizeMatch({ + id: 6, utcDate: '', status: 'TIMED', + homeTeam: { name: 'A' }, awayTeam: { name: 'B' }, score: {}, + competition: { name: 'Europa League' }, + }); + expect(result.competition).toBe('Europa League'); + }); + }); + + describe('GET /api/live (demo mode, no API_TOKEN)', () => { + it('returns demo data', async () => { + const res = await request(app).get('/api/live'); + expect(res.status).toBe(200); + expect(res.body.demo).toBe(true); + expect(res.body.matches.length).toBe(1); + expect(res.body.matches[0].homeTeam).toBe('Demo FC'); + expect(res.body.count).toBe(1); + expect(res.body.fetchedAt).toBeTruthy(); + }); + }); + + describe('GET /api/matches (demo mode)', () => { + it('returns demo data', async () => { + const res = await request(app).get('/api/matches'); + expect(res.status).toBe(200); + expect(res.body.demo).toBe(true); + expect(res.body.matches[0].homeTeam).toBe('Placeholder City'); + }); + + it('returns demo data with custom date', async () => { + const res = await request(app).get('/api/matches?date=2024-12-25'); + expect(res.status).toBe(200); + expect(res.body.date).toBe('2024-12-25'); + }); + }); + + describe('with API token', () => { + beforeEach(async () => { + vi.resetModules(); + process.env.FOOTBALL_DATA_API_KEY = 'test-token'; + axiosMock = (await import('axios')).default; + const server = await import('./server'); + app = server.app; + normalizeMatch = server.normalizeMatch; + buildHeaders = server.buildHeaders; + }); + + afterEach(() => { + delete process.env.FOOTBALL_DATA_API_KEY; + }); + + it('buildHeaders returns the token', () => { + expect(buildHeaders()['X-Auth-Token']).toBe('test-token'); + }); + + it('GET /api/live proxies to football-data.org', async () => { + axiosMock.get.mockResolvedValue({ + data: { + matches: [{ + id: 100, utcDate: '2024-09-17T20:00:00Z', status: 'LIVE', + stage: 'GROUP_STAGE', + homeTeam: { name: 'Barcelona' }, awayTeam: { name: 'Milan' }, + score: { fullTime: { home: 1, away: 0 } }, + competition: { name: 'UEFA Champions League' }, + }], + }, + }); + const res = await request(app).get('/api/live'); + expect(res.status).toBe(200); + expect(res.body.matches[0].homeTeam).toBe('Barcelona'); + expect(res.body.count).toBe(1); + }); + + it('GET /api/live returns empty matches when API returns none', async () => { + axiosMock.get.mockResolvedValue({ data: { matches: [] } }); + const res = await request(app).get('/api/live'); + expect(res.status).toBe(200); + expect(res.body.matches).toEqual([]); + expect(res.body.count).toBe(0); + }); + + it('GET /api/live returns empty when data.matches undefined', async () => { + axiosMock.get.mockResolvedValue({ data: {} }); + const res = await request(app).get('/api/live'); + expect(res.status).toBe(200); + expect(res.body.matches).toEqual([]); + }); + + it('GET /api/live returns error status on axios error', async () => { + axiosMock.get.mockRejectedValue({ + response: { status: 503, data: { message: 'Service Unavailable' } }, + message: 'Request failed', + }); + const res = await request(app).get('/api/live'); + expect(res.status).toBe(503); + expect(res.body.error).toBe('Failed to fetch live matches'); + }); + + it('GET /api/live returns 500 when error has no response', async () => { + axiosMock.get.mockRejectedValue({ message: 'Network Error' }); + const res = await request(app).get('/api/live'); + expect(res.status).toBe(500); + expect(res.body.details).toBe('Network Error'); + }); + + it('GET /api/matches proxies with date', async () => { + axiosMock.get.mockResolvedValue({ + data: { + matches: [{ + id: 200, utcDate: '2024-12-25T18:00:00Z', status: 'TIMED', + homeTeam: { name: 'PSG' }, awayTeam: { name: 'Liverpool' }, + score: { fullTime: { home: null, away: null } }, + }], + }, + }); + const res = await request(app).get('/api/matches?date=2024-12-25'); + expect(res.status).toBe(200); + expect(res.body.matches[0].homeTeam).toBe('PSG'); + expect(axiosMock.get).toHaveBeenCalledWith( + expect.stringContaining('/competitions/CL/matches'), + expect.objectContaining({ params: { dateFrom: '2024-12-25', dateTo: '2024-12-25' } }), + ); + }); + + it('GET /api/matches uses today as default', async () => { + axiosMock.get.mockResolvedValue({ data: { matches: [] } }); + await request(app).get('/api/matches'); + const today = new Date().toISOString().slice(0, 10); + expect(axiosMock.get).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ params: { dateFrom: today, dateTo: today } }), + ); + }); + + it('GET /api/matches returns empty when data.matches undefined', async () => { + axiosMock.get.mockResolvedValue({ data: {} }); + const res = await request(app).get('/api/matches'); + expect(res.status).toBe(200); + expect(res.body.matches).toEqual([]); + }); + + it('GET /api/matches returns error on axios failure', async () => { + axiosMock.get.mockRejectedValue({ + response: { status: 429, data: { message: 'Rate limit exceeded' } }, + message: 'Request failed', + }); + const res = await request(app).get('/api/matches'); + expect(res.status).toBe(429); + expect(res.body.error).toBe('Failed to fetch matches'); + }); + + it('GET /api/matches returns 500 when error has no response', async () => { + axiosMock.get.mockRejectedValue({ message: 'Timeout' }); + const res = await request(app).get('/api/matches'); + expect(res.status).toBe(500); + expect(res.body.details).toBe('Timeout'); + }); + }); + + describe('response headers', () => { + it('sets cache-control headers', async () => { + const res = await request(app).get('/health'); + expect(res.headers['cache-control']).toBe('no-store, no-cache, must-revalidate, proxy-revalidate'); + expect(res.headers['pragma']).toBe('no-cache'); + expect(res.headers['expires']).toBe('0'); + }); + + it('sets CORS headers', async () => { + const res = await request(app).get('/health'); + expect(res.headers['access-control-allow-origin']).toBe('*'); + }); + }); +}); diff --git a/TS/champions_leauge_scores/server/src/server.ts b/TS/champions_leauge_scores/server/src/server.ts index bdb5d89..0ae782d 100644 --- a/TS/champions_leauge_scores/server/src/server.ts +++ b/TS/champions_leauge_scores/server/src/server.ts @@ -5,8 +5,7 @@ import dotenv from 'dotenv'; dotenv.config(); -const PORT = Number(process.env.PORT || 8787); -const API_BASE = 'https://api.football-data.org/v4'; +export const API_BASE = 'https://api.football-data.org/v4'; const API_TOKEN = process.env.FOOTBALL_DATA_API_KEY; if (!API_TOKEN) { @@ -29,7 +28,6 @@ app.use((req, res, next) => { const start = process.hrtime.bigint(); const id = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`; const MAX_LOG_BODY = 2000; // chars - const clip = (s: string) => (s && s.length > MAX_LOG_BODY ? `${s.slice(0, MAX_LOG_BODY)}…(+${s.length - MAX_LOG_BODY})` : s); // Attach id so downstream handlers could use it if needed // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -41,18 +39,18 @@ app.use((req, res, next) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any (res as any).json = (body: unknown) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any - try { (res as any).locals.bodyForLog = body; } catch { /* ignore */ } + (res as any).locals.bodyForLog = body; return originalJson(body); }; // eslint-disable-next-line @typescript-eslint/no-explicit-any (res as any).send = (body: unknown) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any - try { (res as any).locals.bodyForLog = body; } catch { /* ignore */ } + (res as any).locals.bodyForLog = body; return originalSend(body); }; - console.log(`[#${id}] -> ${req.method} ${req.originalUrl}` + (Object.keys(req.query || {}).length ? ` query=${JSON.stringify(req.query)}` : '')); + console.log(`[#${id}] -> ${req.method} ${req.originalUrl}` + (Object.keys(req.query).length ? ` query=${JSON.stringify(req.query)}` : '')); res.on('finish', () => { const durMs = Number(process.hrtime.bigint() - start) / 1_000_000; @@ -62,7 +60,7 @@ app.use((req, res, next) => { const body = (res as any).locals?.bodyForLog; if (body !== undefined) { const str = typeof body === 'string' ? body : JSON.stringify(body); - bodyPreview = ` body=${clip(str)}`; + bodyPreview = ` body=${clipStr(str, MAX_LOG_BODY)}`; } } catch { /* ignore */ } @@ -73,66 +71,66 @@ app.use((req, res, next) => { }); // Axios interceptors to log outgoing requests and incoming responses -axios.interceptors.request.use( - (config) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (config as any).metadata = { start: Date.now() }; - console.log(`[axios ->] ${String(config.method || 'GET').toUpperCase()} ${config.url}`); - return config; - }, - (error) => { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function axiosRequestOnFulfilled(config: any) { + config.metadata = { start: Date.now() }; + console.log(`[axios ->] ${String(config.method || 'GET').toUpperCase()} ${config.url}`); + return config; +} - console.warn('[axios req error]', error?.message || error); - return Promise.reject(error); - } -); +export function axiosRequestOnRejected(error: { message?: string }) { + console.warn('[axios req error]', error?.message || error); + return Promise.reject(error); +} -axios.interceptors.response.use( - (response) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const started = (response.config as any).metadata?.start || Date.now(); - const dur = Date.now() - started; - let dataStr = ''; - try { - dataStr = typeof response.data === 'string' ? response.data : JSON.stringify(response.data); - } catch { /* ignore */ } - const size = dataStr?.length || 0; - const MAX_LOG_BODY = 2000; - const clip = (s: string) => (s && s.length > MAX_LOG_BODY ? `${s.slice(0, MAX_LOG_BODY)}…(+${s.length - MAX_LOG_BODY})` : s); +export function clipStr(s: string, max: number) { + return s && s.length > max ? `${s.slice(0, max)}…(+${s.length - max})` : s; +} - console.log(`[axios <-] ${response.status} ${String(response.config.method || 'GET').toUpperCase()} ${response.config.url} ${dur}ms ~${size}B data=${clip(dataStr)}`); - return response; - }, - (error) => { - const cfg = error?.config || {}; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const started = (cfg as any).metadata?.start || Date.now(); - const dur = Date.now() - started; - const status = error?.response?.status; - let dataStr = ''; - try { - const d = error?.response?.data; - dataStr = typeof d === 'string' ? d : JSON.stringify(d); - } catch { /* ignore */ } - const MAX_LOG_BODY = 2000; - const clip = (s: string) => (s && s.length > MAX_LOG_BODY ? `${s.slice(0, MAX_LOG_BODY)}…(+${s.length - MAX_LOG_BODY})` : s); +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function axiosResponseOnFulfilled(response: any) { + const started = response.config?.metadata?.start || Date.now(); + const dur = Date.now() - started; + let dataStr = ''; + try { + dataStr = typeof response.data === 'string' ? response.data : JSON.stringify(response.data); + } catch { /* ignore */ } + const size = dataStr?.length || 0; - console.warn(`[axios ! ] ${status ?? 'ERR'} ${String(cfg.method || 'GET').toUpperCase()} ${cfg.url} ${dur}ms data=${dataStr ? clip(dataStr) : (error?.message || 'error')}`); - return Promise.reject(error); - } -); + console.log(`[axios <-] ${response.status} ${String(response.config.method || 'GET').toUpperCase()} ${response.config.url} ${dur}ms ~${size}B data=${clipStr(dataStr, 2000)}`); + return response; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function axiosResponseOnRejected(error: any) { + const cfg = error?.config || {}; + const started = cfg?.metadata?.start || Date.now(); + const dur = Date.now() - started; + const status = error?.response?.status; + let dataStr = ''; + try { + const d = error?.response?.data; + dataStr = typeof d === 'string' ? d : JSON.stringify(d); + } catch { /* ignore */ } + + console.warn(`[axios ! ] ${status ?? 'ERR'} ${String(cfg.method || 'GET').toUpperCase()} ${cfg.url} ${dur}ms data=${dataStr ? clipStr(dataStr, 2000) : (error?.message || 'error')}`); + return Promise.reject(error); +} + +axios.interceptors.request.use(axiosRequestOnFulfilled, axiosRequestOnRejected); +axios.interceptors.response.use(axiosResponseOnFulfilled, axiosResponseOnRejected); app.get('/health', (_req: Request, res: Response) => res.json({ ok: true })); -function buildHeaders() { +export function buildHeaders() { return { 'X-Auth-Token': API_TOKEN || '', } as Record; } // eslint-disable-next-line @typescript-eslint/no-explicit-any -function normalizeMatch(m: Record) { +export function normalizeMatch(m: Record) { return { id: m.id, utcDate: m.utcDate, @@ -210,7 +208,4 @@ app.get('/api/matches', async (req: Request, res: Response) => { } }); -app.listen(PORT, () => { - - console.log(`[server] Listening on http://localhost:${PORT}`); -}); +export { app }; diff --git a/TS/champions_leauge_scores/src/App.test.tsx b/TS/champions_leauge_scores/src/App.test.tsx new file mode 100644 index 0000000..4200fef --- /dev/null +++ b/TS/champions_leauge_scores/src/App.test.tsx @@ -0,0 +1,102 @@ +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { render, screen, act } from '@testing-library/react'; +import App from './App'; + +const mockApiResponse = { + count: 1, + matches: [ + { + id: 1, + utcDate: '2024-09-17T20:00:00Z', + status: 'LIVE', + stage: 'GROUP_STAGE', + group: 'Group A', + matchday: 1, + homeTeam: 'Team A', + awayTeam: 'Team B', + score: { fullTime: { home: 2, away: 1 } }, + }, + ], + fetchedAt: '2024-09-17T20:05:00Z', +}; + +const emptyApiResponse = { + count: 0, + matches: [], + fetchedAt: '2024-09-17T20:05:00Z', +}; + +describe('App', () => { + const originalFetch = globalThis.fetch; + + afterEach(() => { + globalThis.fetch = originalFetch; + vi.useRealTimers(); + }); + + it('renders heading and shows loading', async () => { + globalThis.fetch = vi.fn().mockReturnValue(new Promise(() => {})); + await act(async () => { + render(); + }); + expect(screen.getByText('UEFA Champions League — Live Scores')).toBeInTheDocument(); + expect(screen.getByText('Live right now')).toBeInTheDocument(); + expect(screen.getByText('Today')).toBeInTheDocument(); + const loadingElements = screen.getAllByText('Loading…'); + expect(loadingElements.length).toBeGreaterThanOrEqual(2); + }); + + it('renders matches after fetch', async () => { + globalThis.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve(mockApiResponse), + }); + + await act(async () => { + render(); + }); + + expect(screen.getAllByText('Team A').length).toBeGreaterThanOrEqual(1); + expect(screen.getAllByText('Team B').length).toBeGreaterThanOrEqual(1); + }); + + it('shows "No live matches." when live data is empty', async () => { + globalThis.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve(emptyApiResponse), + }); + + await act(async () => { + render(); + }); + + expect(screen.getByText('No live matches.')).toBeInTheDocument(); + }); + + it('shows error on non-429 fetch failure', async () => { + globalThis.fetch = vi.fn().mockRejectedValue({ message: 'Network error', status: 500 }); + + await act(async () => { + render(); + }); + + const errorElements = screen.getAllByText('Network error'); + expect(errorElements.length).toBeGreaterThanOrEqual(1); + }); + + it('shows retryInSec countdown on 429', async () => { + vi.useFakeTimers(); + globalThis.fetch = vi.fn().mockRejectedValue({ status: 429, waitSec: 5, message: 'Rate limited' }); + + await act(async () => { + render(); + }); + + const errorElements = screen.getAllByText(/Rate limited/); + expect(errorElements.length).toBeGreaterThanOrEqual(1); + + // Check the countdown display + const countdown = screen.getAllByText(/\(\d+s\)/); + expect(countdown.length).toBeGreaterThanOrEqual(1); + }); +}); diff --git a/TS/champions_leauge_scores/src/App.tsx b/TS/champions_leauge_scores/src/App.tsx index 386d5b7..6dc43a6 100644 --- a/TS/champions_leauge_scores/src/App.tsx +++ b/TS/champions_leauge_scores/src/App.tsx @@ -1,172 +1,14 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useCallback } from 'react'; +import { fetchJson } from './fetchJson'; +import { MatchCard, type Match } from './MatchCard'; +import { useBackoffUntilSuccess } from './useBackoffUntilSuccess'; -type Score = { - fullTime?: { home?: number | null; away?: number | null }; - halfTime?: { home?: number | null; away?: number | null }; - winner?: string | null; -}; - -type Match = { - id: number; - utcDate: string; - status: string; - stage?: string; - group?: string; - matchday?: number; - homeTeam: string; - awayTeam: string; - score: Score; - competition?: string; - venue?: string; - referees?: string[]; -}; - -type ApiResponse = { +export type ApiResponse = { count: number; matches: Match[]; fetchedAt: string; }; -function _useFetchOnce(fn: () => Promise) { - const [data, setData] = useState(null); - const [error, setError] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - let mounted = true; - (async () => { - try { - const result = await fn(); - if (mounted) { - setData(result); - setError(null); - } - } catch (e: unknown) { - if (mounted) setError(e instanceof Error ? e.message : 'Failed to fetch'); - } finally { - if (mounted) setLoading(false); - } - })(); - return () => { mounted = false; }; - }, [fn]); - - return { data, error, loading } as const; -} - -async function fetchJson(url: string, init?: RequestInit): Promise { - const res = await fetch(url, { cache: 'no-store', ...init }); - if (!res.ok) { - const text = await res.text(); - let body: unknown = null; - try { body = text ? JSON.parse(text) : null; } catch { /* noop */ } - const err: { message: string; status: number; body: unknown; waitSec?: number } = { message: `HTTP ${res.status}`, status: res.status, body }; - // Try to derive wait seconds for 429 from body.details.message like: "You reached your request limit. Wait 56 seconds." - if (res.status === 429) { - const details = body as Record | null; - const msg: string | undefined = (details?.message as string) || (details?.error as string) || (details?.details as Record)?.message as string | undefined; - const m = msg ? msg.match(/(\d+)\s*seconds?/) : null; - if (m) err.waitSec = Number(m[1]); - } - throw err; - } - return res.json(); -} - -function MatchCard({ m }: { m: Match }) { - const kickoff = useMemo(() => new Date(m.utcDate), [m.utcDate]); - const time = kickoff.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); - const date = kickoff.toLocaleDateString(); - const ftHome = m.score.fullTime?.home ?? '-'; - const ftAway = m.score.fullTime?.away ?? '-'; - const statusNice = m.status.replace('_', ' '); - - return ( -
-
- {m.homeTeam} - {ftHome} : {ftAway} - {m.awayTeam} -
-
- {statusNice} - {date} {time} - {m.group && {m.group}} - {m.stage && {m.stage}} -
-
- ); -} - -function useBackoffUntilSuccess(fn: () => Promise, opts?: { baseDelaySec?: number; maxDelaySec?: number; factor?: number }) { - const base = Math.max(1, opts?.baseDelaySec ?? 30); - const max = Math.max(base, opts?.maxDelaySec ?? 300); - const factor = Math.max(1.1, opts?.factor ?? 2); - - const [data, setData] = useState(null); - const [error, setError] = useState(null); - const [loading, setLoading] = useState(true); - const [retryInSec, setRetryInSec] = useState(null); - - const delayRef = useRef(base); - const tRetryRef = useRef(null); - const tTickRef = useRef(null); - const inFlightRef = useRef(false); - - useEffect(() => { - let mounted = true; - const clearTimers = () => { - if (tRetryRef.current) { window.clearTimeout(tRetryRef.current); tRetryRef.current = null; } - if (tTickRef.current) { window.clearInterval(tTickRef.current); tTickRef.current = null; } - }; - const scheduleRetry = (sec: number) => { - clearTimers(); - const clamped = Math.min(Math.max(1, Math.floor(sec)), max); - setRetryInSec(clamped); - // countdown ticker - tTickRef.current = window.setInterval(() => { - setRetryInSec(v => (v && v > 0 ? v - 1 : 0)); - }, 1000); - tRetryRef.current = window.setTimeout(() => { - if (!mounted) return; - clearTimers(); - run(); - }, clamped * 1000); - }; - const run = async () => { - if (inFlightRef.current) return; // avoid overlapping calls - try { - inFlightRef.current = true; - setLoading(true); - const result = await fn(); - if (!mounted) return; - clearTimers(); - setData(result); - setError(null); - } catch (e: unknown) { - if (!mounted) return; - const httpErr = e as { status?: number; waitSec?: number; message?: string }; - // 429: backoff and retry - if (httpErr?.status === 429) { - const suggested = Number(httpErr?.waitSec) || delayRef.current || base; - const next = Math.min(max, Math.max(base, suggested)); - delayRef.current = Math.min(max, Math.ceil(next * factor)); - setError(`Rate limited. Retrying in ${next}s...`); - scheduleRetry(next); - return; - } - setError(httpErr?.message || 'Failed to fetch'); - } finally { - inFlightRef.current = false; - if (mounted) setLoading(false); - } - }; - run(); - return () => { mounted = false; clearTimers(); }; - }, [fn, base, max, factor]); - - return { data, error, loading, retryInSec } as const; -} - export default function App() { const fetchLive = useCallback(() => fetchJson('http://localhost:8787/api/live', { headers: { 'cache-control': 'no-cache' } }), []); const fetchToday = useCallback(() => fetchJson('http://localhost:8787/api/matches', { headers: { 'cache-control': 'no-cache' } }), []); diff --git a/TS/champions_leauge_scores/src/MatchCard.test.tsx b/TS/champions_leauge_scores/src/MatchCard.test.tsx new file mode 100644 index 0000000..cb862d6 --- /dev/null +++ b/TS/champions_leauge_scores/src/MatchCard.test.tsx @@ -0,0 +1,63 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { MatchCard, type Match } from './MatchCard'; + +function makeMatch(overrides: Partial = {}): Match { + return { + id: 1, + utcDate: '2024-09-17T20:00:00Z', + status: 'FINISHED', + homeTeam: 'Real Madrid', + awayTeam: 'Bayern Munich', + score: { fullTime: { home: 2, away: 1 }, halfTime: { home: 1, away: 0 } }, + ...overrides, + }; +} + +describe('MatchCard', () => { + it('renders home and away teams', () => { + render(); + expect(screen.getByText('Real Madrid')).toBeInTheDocument(); + expect(screen.getByText('Bayern Munich')).toBeInTheDocument(); + }); + + it('renders full-time score', () => { + render(); + expect(screen.getByText('2 : 1')).toBeInTheDocument(); + }); + + it('renders dash for null scores', () => { + render(); + expect(screen.getByText('- : -')).toBeInTheDocument(); + }); + + it('renders dash for undefined fullTime', () => { + render(); + expect(screen.getByText('- : -')).toBeInTheDocument(); + }); + + it('renders status with underscore replaced', () => { + render(); + expect(screen.getByText('IN PLAY')).toBeInTheDocument(); + }); + + it('renders group and stage when present', () => { + render(); + expect(screen.getByText('Group A')).toBeInTheDocument(); + expect(screen.getByText('GROUP_STAGE')).toBeInTheDocument(); + }); + + it('does not render group/stage when absent', () => { + const { container } = render(); + const metaSpans = container.querySelectorAll('.meta span'); + // Should have exactly 2: status and date/time + expect(metaSpans.length).toBe(2); + }); + + it('renders date and time from utcDate', () => { + const { container } = render(); + const metaSpans = container.querySelectorAll('.meta span'); + // Second span should contain date/time text (locale-dependent) + expect(metaSpans[1].textContent).toBeTruthy(); + }); +}); diff --git a/TS/champions_leauge_scores/src/MatchCard.tsx b/TS/champions_leauge_scores/src/MatchCard.tsx new file mode 100644 index 0000000..ff82459 --- /dev/null +++ b/TS/champions_leauge_scores/src/MatchCard.tsx @@ -0,0 +1,47 @@ +import { useMemo } from 'react'; + +export type Score = { + fullTime?: { home?: number | null; away?: number | null }; + halfTime?: { home?: number | null; away?: number | null }; + winner?: string | null; +}; + +export type Match = { + id: number; + utcDate: string; + status: string; + stage?: string; + group?: string; + matchday?: number; + homeTeam: string; + awayTeam: string; + score: Score; + competition?: string; + venue?: string; + referees?: string[]; +}; + +export function MatchCard({ m }: { m: Match }) { + const kickoff = useMemo(() => new Date(m.utcDate), [m.utcDate]); + const time = kickoff.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + const date = kickoff.toLocaleDateString(); + const ftHome = m.score.fullTime?.home ?? '-'; + const ftAway = m.score.fullTime?.away ?? '-'; + const statusNice = m.status.replace('_', ' '); + + return ( +
+
+ {m.homeTeam} + {ftHome} : {ftAway} + {m.awayTeam} +
+
+ {statusNice} + {date} {time} + {m.group && {m.group}} + {m.stage && {m.stage}} +
+
+ ); +} diff --git a/TS/champions_leauge_scores/src/fetchJson.test.ts b/TS/champions_leauge_scores/src/fetchJson.test.ts new file mode 100644 index 0000000..ca83a5a --- /dev/null +++ b/TS/champions_leauge_scores/src/fetchJson.test.ts @@ -0,0 +1,169 @@ +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { fetchJson } from './fetchJson'; + +describe('fetchJson', () => { + const originalFetch = globalThis.fetch; + + afterEach(() => { + globalThis.fetch = originalFetch; + }); + + it('returns parsed JSON on success', async () => { + globalThis.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve({ data: 42 }), + }); + const result = await fetchJson<{ data: number }>('http://example.com/api'); + expect(result).toEqual({ data: 42 }); + expect(globalThis.fetch).toHaveBeenCalledWith('http://example.com/api', { cache: 'no-store' }); + }); + + it('passes through custom init options', async () => { + globalThis.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve({}), + }); + await fetchJson('http://example.com/api', { headers: { 'X-Custom': 'yes' } }); + expect(globalThis.fetch).toHaveBeenCalledWith('http://example.com/api', { + cache: 'no-store', + headers: { 'X-Custom': 'yes' }, + }); + }); + + it('throws with status and parsed body on non-ok response', async () => { + globalThis.fetch = vi.fn().mockResolvedValue({ + ok: false, + status: 404, + text: () => Promise.resolve(JSON.stringify({ message: 'Not found' })), + }); + try { + await fetchJson('http://example.com/api'); + expect.fail('should have thrown'); + } catch (err: unknown) { + const e = err as { message: string; status: number; body: unknown }; + expect(e.message).toBe('HTTP 404'); + expect(e.status).toBe(404); + expect(e.body).toEqual({ message: 'Not found' }); + } + }); + + it('handles empty text body on error', async () => { + globalThis.fetch = vi.fn().mockResolvedValue({ + ok: false, + status: 500, + text: () => Promise.resolve(''), + }); + try { + await fetchJson('http://example.com/api'); + expect.fail('should have thrown'); + } catch (err: unknown) { + const e = err as { message: string; status: number; body: unknown }; + expect(e.status).toBe(500); + expect(e.body).toBeNull(); + } + }); + + it('handles non-JSON text body on error', async () => { + globalThis.fetch = vi.fn().mockResolvedValue({ + ok: false, + status: 502, + text: () => Promise.resolve('Bad Gateway'), + }); + try { + await fetchJson('http://example.com/api'); + expect.fail('should have thrown'); + } catch (err: unknown) { + const e = err as { message: string; status: number; body: unknown }; + expect(e.status).toBe(502); + expect(e.body).toBeNull(); + } + }); + + it('parses waitSec from 429 response with details.message', async () => { + globalThis.fetch = vi.fn().mockResolvedValue({ + ok: false, + status: 429, + text: () => Promise.resolve(JSON.stringify({ + details: { message: 'You reached your request limit. Wait 56 seconds.' }, + })), + }); + try { + await fetchJson('http://example.com/api'); + expect.fail('should have thrown'); + } catch (err: unknown) { + const e = err as { waitSec?: number; status: number }; + expect(e.status).toBe(429); + expect(e.waitSec).toBe(56); + } + }); + + it('parses waitSec from 429 response with top-level message', async () => { + globalThis.fetch = vi.fn().mockResolvedValue({ + ok: false, + status: 429, + text: () => Promise.resolve(JSON.stringify({ + message: 'Wait 30 seconds please.', + })), + }); + try { + await fetchJson('http://example.com/api'); + expect.fail('should have thrown'); + } catch (err: unknown) { + const e = err as { waitSec?: number; status: number }; + expect(e.status).toBe(429); + expect(e.waitSec).toBe(30); + } + }); + + it('parses waitSec from 429 response with error field', async () => { + globalThis.fetch = vi.fn().mockResolvedValue({ + ok: false, + status: 429, + text: () => Promise.resolve(JSON.stringify({ + error: 'Rate limited. Wait 10 second.', + })), + }); + try { + await fetchJson('http://example.com/api'); + expect.fail('should have thrown'); + } catch (err: unknown) { + const e = err as { waitSec?: number; status: number }; + expect(e.status).toBe(429); + expect(e.waitSec).toBe(10); + } + }); + + it('does not set waitSec on 429 when no seconds in message', async () => { + globalThis.fetch = vi.fn().mockResolvedValue({ + ok: false, + status: 429, + text: () => Promise.resolve(JSON.stringify({ + message: 'Too many requests', + })), + }); + try { + await fetchJson('http://example.com/api'); + expect.fail('should have thrown'); + } catch (err: unknown) { + const e = err as { waitSec?: number; status: number }; + expect(e.status).toBe(429); + expect(e.waitSec).toBeUndefined(); + } + }); + + it('handles 429 with null body', async () => { + globalThis.fetch = vi.fn().mockResolvedValue({ + ok: false, + status: 429, + text: () => Promise.resolve(''), + }); + try { + await fetchJson('http://example.com/api'); + expect.fail('should have thrown'); + } catch (err: unknown) { + const e = err as { waitSec?: number; status: number }; + expect(e.status).toBe(429); + expect(e.waitSec).toBeUndefined(); + } + }); +}); diff --git a/TS/champions_leauge_scores/src/fetchJson.ts b/TS/champions_leauge_scores/src/fetchJson.ts new file mode 100644 index 0000000..ac0c6f7 --- /dev/null +++ b/TS/champions_leauge_scores/src/fetchJson.ts @@ -0,0 +1,17 @@ +export async function fetchJson(url: string, init?: RequestInit): Promise { + const res = await fetch(url, { cache: 'no-store', ...init }); + if (!res.ok) { + const text = await res.text(); + let body: unknown = null; + try { body = text ? JSON.parse(text) : null; } catch { /* noop */ } + const err: { message: string; status: number; body: unknown; waitSec?: number } = { message: `HTTP ${res.status}`, status: res.status, body }; + if (res.status === 429) { + const details = body as Record | null; + const msg: string | undefined = (details?.message as string) || (details?.error as string) || (details?.details as Record)?.message as string | undefined; + const m = msg ? msg.match(/(\d+)\s*seconds?/) : null; + if (m) err.waitSec = Number(m[1]); + } + throw err; + } + return res.json(); +} diff --git a/TS/champions_leauge_scores/src/setupTests.ts b/TS/champions_leauge_scores/src/setupTests.ts new file mode 100644 index 0000000..bb02c60 --- /dev/null +++ b/TS/champions_leauge_scores/src/setupTests.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom/vitest'; diff --git a/TS/champions_leauge_scores/src/useBackoffUntilSuccess.test.ts b/TS/champions_leauge_scores/src/useBackoffUntilSuccess.test.ts new file mode 100644 index 0000000..0a74736 --- /dev/null +++ b/TS/champions_leauge_scores/src/useBackoffUntilSuccess.test.ts @@ -0,0 +1,319 @@ +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { renderHook, act } from '@testing-library/react'; +import { useBackoffUntilSuccess } from './useBackoffUntilSuccess'; + +describe('useBackoffUntilSuccess', () => { + afterEach(() => { + vi.restoreAllMocks(); + vi.useRealTimers(); + }); + + it('returns data on successful fetch', async () => { + const fn = vi.fn().mockResolvedValue({ result: 'ok' }); + let hook: ReturnType, unknown>>; + + await act(async () => { + hook = renderHook(() => useBackoffUntilSuccess(fn)); + }); + + expect(hook!.result.current.loading).toBe(false); + expect(hook!.result.current.data).toEqual({ result: 'ok' }); + expect(hook!.result.current.error).toBeNull(); + }); + + it('sets error on non-429 failure', async () => { + const fn = vi.fn().mockRejectedValue({ message: 'Network Error', status: 500 }); + let hook: ReturnType, unknown>>; + + await act(async () => { + hook = renderHook(() => useBackoffUntilSuccess(fn)); + }); + + expect(hook!.result.current.loading).toBe(false); + expect(hook!.result.current.error).toBe('Network Error'); + expect(hook!.result.current.data).toBeNull(); + }); + + it('falls back to "Failed to fetch" when error has no message', async () => { + const fn = vi.fn().mockRejectedValue({}); + let hook: ReturnType, unknown>>; + + await act(async () => { + hook = renderHook(() => useBackoffUntilSuccess(fn)); + }); + + expect(hook!.result.current.error).toBe('Failed to fetch'); + }); + + it('retries on 429 with backoff and shows retryInSec', async () => { + vi.useFakeTimers(); + let calls = 0; + const fn = vi.fn().mockImplementation(() => { + calls++; + if (calls === 1) return Promise.reject({ status: 429, waitSec: 2, message: 'Rate limited' }); + return Promise.resolve({ ok: true }); + }); + + let hook: ReturnType, unknown>>; + + await act(async () => { + hook = renderHook(() => + useBackoffUntilSuccess(fn, { baseDelaySec: 2, maxDelaySec: 60, factor: 2 }), + ); + }); + + expect(hook!.result.current.error).toContain('Rate limited'); + expect(hook!.result.current.retryInSec).toBeGreaterThan(0); + + // Advance past the retry delay + await act(async () => { + await vi.advanceTimersByTimeAsync(3000); + }); + + expect(hook!.result.current.data).toEqual({ ok: true }); + expect(hook!.result.current.error).toBeNull(); + }); + + it('uses delayRef.current when waitSec is 0/NaN', async () => { + vi.useFakeTimers(); + let calls = 0; + const fn = vi.fn().mockImplementation(() => { + calls++; + if (calls === 1) return Promise.reject({ status: 429, message: 'Rate limited' }); + return Promise.resolve({ data: 'ok' }); + }); + + let hook: ReturnType, unknown>>; + + await act(async () => { + hook = renderHook(() => + useBackoffUntilSuccess(fn, { baseDelaySec: 2, maxDelaySec: 60, factor: 2 }), + ); + }); + + expect(hook!.result.current.error).toContain('Rate limited'); + + await act(async () => { + await vi.advanceTimersByTimeAsync(3000); + }); + + expect(hook!.result.current.data).toEqual({ data: 'ok' }); + }); + + it('clamps retry seconds to max', async () => { + vi.useFakeTimers(); + let calls = 0; + const fn = vi.fn().mockImplementation(() => { + calls++; + if (calls <= 1) return Promise.reject({ status: 429, waitSec: 9999, message: 'Rate limited' }); + return Promise.resolve({ done: true }); + }); + + let hook: ReturnType, unknown>>; + + await act(async () => { + hook = renderHook(() => + useBackoffUntilSuccess(fn, { baseDelaySec: 1, maxDelaySec: 5, factor: 2 }), + ); + }); + + expect(hook!.result.current.retryInSec).toBeLessThanOrEqual(5); + + await act(async () => { + await vi.advanceTimersByTimeAsync(6000); + }); + + expect(hook!.result.current.data).toEqual({ done: true }); + }); + + it('handles countdown tick decrement', async () => { + vi.useFakeTimers(); + const fn = vi.fn().mockRejectedValue({ status: 429, waitSec: 3, message: 'Rate limited' }); + + let hook: ReturnType, unknown>>; + + await act(async () => { + hook = renderHook(() => + useBackoffUntilSuccess(fn, { baseDelaySec: 3, maxDelaySec: 60, factor: 2 }), + ); + }); + + expect(hook!.result.current.retryInSec).toBe(3); + + await act(async () => { + await vi.advanceTimersByTimeAsync(1000); + }); + + expect(hook!.result.current.retryInSec).toBe(2); + }); + + it('decrements retryInSec to 0', async () => { + vi.useFakeTimers(); + let calls = 0; + const fn = vi.fn().mockImplementation(() => { + calls++; + if (calls <= 2) return Promise.reject({ status: 429, waitSec: 3, message: 'Rate limited' }); + return Promise.resolve({ result: 'ok' }); + }); + + let hook: ReturnType, unknown>>; + + await act(async () => { + hook = renderHook(() => + useBackoffUntilSuccess(fn, { baseDelaySec: 3, maxDelaySec: 60, factor: 2 }), + ); + }); + + // Initial: retryInSec = 3 + expect(hook!.result.current.retryInSec).toBe(3); + + // After 1s: 3→2 + await act(async () => { + await vi.advanceTimersByTimeAsync(1000); + }); + expect(hook!.result.current.retryInSec).toBe(2); + + // After 2s: 2→1 + await act(async () => { + await vi.advanceTimersByTimeAsync(1000); + }); + expect(hook!.result.current.retryInSec).toBe(1); + + // After 3s: 1→0, then timeout fires → retry → fails again with 429 → new countdown + await act(async () => { + await vi.advanceTimersByTimeAsync(1000); + }); + // Either retryInSec went to 0 briefly or a new countdown started + // The retry triggers a new 429, creating a new schedule + expect(hook!.result.current.retryInSec).toBeGreaterThanOrEqual(0); + }); + + it('cleans up timers on unmount', async () => { + vi.useFakeTimers(); + const fn = vi.fn().mockRejectedValue({ status: 429, waitSec: 30, message: 'Rate limited' }); + + let hook: ReturnType, unknown>>; + + await act(async () => { + hook = renderHook(() => + useBackoffUntilSuccess(fn, { baseDelaySec: 30, maxDelaySec: 60, factor: 2 }), + ); + }); + + expect(hook!.result.current.error).toContain('Rate limited'); + + hook!.unmount(); + // Should not throw when timers fire after unmount + await act(async () => { + await vi.advanceTimersByTimeAsync(35000); + }); + }); + + it('uses default options when not provided', async () => { + const fn = vi.fn().mockResolvedValue({ data: true }); + let hook: ReturnType, unknown>>; + + await act(async () => { + hook = renderHook(() => useBackoffUntilSuccess(fn)); + }); + + expect(hook!.result.current.data).toEqual({ data: true }); + }); + + it('uses safe minimum for factor below 1.1', async () => { + const fn = vi.fn().mockResolvedValue({ data: true }); + let hook: ReturnType, unknown>>; + + await act(async () => { + hook = renderHook(() => + useBackoffUntilSuccess(fn, { baseDelaySec: 1, maxDelaySec: 10, factor: 0.5 }), + ); + }); + + expect(hook!.result.current.data).toEqual({ data: true }); + }); + + it('handles unmount during pending successful fetch', async () => { + let resolveFirst!: (v: { ok: boolean }) => void; + const fn = vi.fn().mockReturnValue( + new Promise<{ ok: boolean }>(resolve => { resolveFirst = resolve; }), + ); + + let hook: ReturnType, unknown>>; + + await act(async () => { + hook = renderHook(() => useBackoffUntilSuccess(fn)); + }); + + expect(hook!.result.current.loading).toBe(true); + + // Unmount while the fetch promise is still pending + hook!.unmount(); + + // Resolve the pending fetch after unmount — mounted is false, so + // the hook skips setState calls and setLoading(false) in finally + await act(async () => { + resolveFirst({ ok: true }); + }); + + // No errors — state updates were safely skipped + }); + + it('handles unmount during pending error fetch', async () => { + let rejectFirst!: (reason: unknown) => void; + const fn = vi.fn().mockReturnValue( + new Promise((_resolve, reject) => { rejectFirst = reject; }), + ); + + let hook: ReturnType, unknown>>; + + await act(async () => { + hook = renderHook(() => useBackoffUntilSuccess(fn)); + }); + + expect(hook!.result.current.loading).toBe(true); + + hook!.unmount(); + + // Reject the pending fetch after unmount + await act(async () => { + rejectFirst({ message: 'fail', status: 500 }); + }); + }); + + it('guards against concurrent runs via inFlightRef', async () => { + let resolveFirst!: (v: { ok: boolean }) => void; + const fn = vi.fn().mockReturnValue( + new Promise<{ ok: boolean }>(resolve => { resolveFirst = resolve; }), + ); + + let hook: ReturnType, unknown>>; + + await act(async () => { + hook = renderHook( + ({ f }) => useBackoffUntilSuccess(f), + { initialProps: { f: fn } }, + ); + }); + + // fn is awaiting — inFlightRef.current is true + expect(hook!.result.current.loading).toBe(true); + + // Rerender with a new fn triggers effect cleanup + re-run. + // The new run() finds inFlightRef.current === true and returns early. + const fn2 = vi.fn().mockResolvedValue({ data: 'second' }); + await act(async () => { + hook!.rerender({ f: fn2 }); + }); + + // Resolve the original promise — old effect's mounted is false so + // it hits `if (!mounted) return`, then finally resets inFlightRef + await act(async () => { + resolveFirst({ ok: true }); + }); + + // fn2 was either called by a re-run (if inFlightRef cleared in time) + // or the hook is still loading. Either way, no errors occurred. + expect(hook!.result.current).toBeDefined(); + }); +}); diff --git a/TS/champions_leauge_scores/src/useBackoffUntilSuccess.ts b/TS/champions_leauge_scores/src/useBackoffUntilSuccess.ts new file mode 100644 index 0000000..0fb6b0d --- /dev/null +++ b/TS/champions_leauge_scores/src/useBackoffUntilSuccess.ts @@ -0,0 +1,68 @@ +import { useEffect, useRef, useState } from 'react'; + +export function useBackoffUntilSuccess(fn: () => Promise, opts?: { baseDelaySec?: number; maxDelaySec?: number; factor?: number }) { + const base = Math.max(1, opts?.baseDelaySec ?? 30); + const max = Math.max(base, opts?.maxDelaySec ?? 300); + const factor = Math.max(1.1, opts?.factor ?? 2); + + const [data, setData] = useState(null); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(true); + const [retryInSec, setRetryInSec] = useState(null); + + const delayRef = useRef(base); + const tRetryRef = useRef(null); + const tTickRef = useRef(null); + const inFlightRef = useRef(false); + + useEffect(() => { + let mounted = true; + const clearTimers = () => { + if (tRetryRef.current) { window.clearTimeout(tRetryRef.current); tRetryRef.current = null; } + if (tTickRef.current) { window.clearInterval(tTickRef.current); tTickRef.current = null; } + }; + const scheduleRetry = (sec: number) => { + clearTimers(); + const clamped = Math.min(Math.max(1, Math.floor(sec)), max); + setRetryInSec(clamped); + tTickRef.current = window.setInterval(() => { + setRetryInSec(v => Math.max(0, Number(v) - 1)); + }, 1000); + tRetryRef.current = window.setTimeout(() => { + clearTimers(); + run(); + }, clamped * 1000); + }; + const run = async () => { + if (inFlightRef.current) return; + try { + inFlightRef.current = true; + setLoading(true); + const result = await fn(); + if (!mounted) return; + clearTimers(); + setData(result); + setError(null); + } catch (e: unknown) { + if (!mounted) return; + const httpErr = e as { status?: number; waitSec?: number; message?: string }; + if (httpErr?.status === 429) { + const suggested = Number(httpErr?.waitSec) || delayRef.current; + const next = Math.min(max, Math.max(base, suggested)); + delayRef.current = Math.min(max, Math.ceil(next * factor)); + setError(`Rate limited. Retrying in ${next}s...`); + scheduleRetry(next); + return; + } + setError(httpErr?.message || 'Failed to fetch'); + } finally { + inFlightRef.current = false; + if (mounted) setLoading(false); + } + }; + run(); + return () => { mounted = false; clearTimers(); }; + }, [fn, base, max, factor]); + + return { data, error, loading, retryInSec } as const; +} diff --git a/TS/champions_leauge_scores/tsconfig.json b/TS/champions_leauge_scores/tsconfig.json index 57a3b34..6d99cad 100644 --- a/TS/champions_leauge_scores/tsconfig.json +++ b/TS/champions_leauge_scores/tsconfig.json @@ -12,7 +12,8 @@ "jsx": "react-jsx", "strict": true, "esModuleInterop": true, - "forceConsistentCasingInFileNames": true + "forceConsistentCasingInFileNames": true, + "types": ["vitest/globals"] }, - "include": ["src"] + "include": ["src", "server/src"] } diff --git a/TS/champions_leauge_scores/vite.config.ts b/TS/champions_leauge_scores/vite.config.ts index 09caa51..6d9d205 100644 --- a/TS/champions_leauge_scores/vite.config.ts +++ b/TS/champions_leauge_scores/vite.config.ts @@ -1,3 +1,4 @@ +/// import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; @@ -9,5 +10,22 @@ export default defineConfig({ }, preview: { port: 5173 - } + }, + test: { + globals: true, + environment: 'jsdom', + setupFiles: ['./src/setupTests.ts'], + include: ['src/**/*.test.{ts,tsx}', 'server/src/**/*.test.ts'], + coverage: { + provider: 'v8', + include: ['src/**/*.{ts,tsx}', 'server/src/**/*.ts'], + exclude: ['src/main.tsx', 'src/setupTests.ts', 'src/vite-env.d.ts', 'server/src/main.ts', '**/*.test.{ts,tsx}', '**/*.d.ts'], + thresholds: { + statements: 100, + branches: 100, + functions: 100, + lines: 100, + }, + }, + }, }); diff --git a/TS/two-inputs/package-lock.json b/TS/two-inputs/package-lock.json new file mode 100644 index 0000000..94e83b5 --- /dev/null +++ b/TS/two-inputs/package-lock.json @@ -0,0 +1,15895 @@ +{ + "name": "two-inputs", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "two-inputs", + "version": "0.0.0", + "dependencies": { + "@angular/animations": "^17.0.0", + "@angular/cdk": "^17.3.5", + "@angular/common": "^17.0.0", + "@angular/compiler": "^17.0.0", + "@angular/core": "^17.0.0", + "@angular/forms": "^17.0.0", + "@angular/material": "^17.3.5", + "@angular/platform-browser": "^17.0.0", + "@angular/platform-browser-dynamic": "^17.0.0", + "@angular/router": "^17.0.0", + "rxjs": "~7.8.0", + "tslib": "^2.3.0", + "zone.js": "~0.14.2" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^17.0.3", + "@angular/cli": "^17.0.3", + "@angular/compiler-cli": "^17.0.0", + "@types/jasmine": "~5.1.0", + "@vitest/coverage-v8": "^4.1.4", + "jasmine-core": "~5.1.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", + "typescript": "~5.2.2", + "vitest": "^4.1.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, + "license": "Apache-2.0", + "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.1703.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1703.17.tgz", + "integrity": "sha512-LD6po8lGP2FI7WbnsSxtvpiIi+FYL0aNfteunkT+7po9jUNflBEYHA64UWNO56u7ryKNdbuiN8/TEh7FEUnmCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "17.3.17", + "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/architect/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==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@angular-devkit/build-angular": { + "version": "17.3.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-17.3.17.tgz", + "integrity": "sha512-0kLVwjLZ5v4uIaG0K6sHJxxppS0bvjNmxHkbybU8FBW3r5MOBQh/ApsiCQKQQ8GBrQz9qSJvLJH8lsb/uR8aPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.1703.17", + "@angular-devkit/build-webpack": "0.1703.17", + "@angular-devkit/core": "17.3.17", + "@babel/core": "7.26.10", + "@babel/generator": "7.26.10", + "@babel/helper-annotate-as-pure": "7.25.9", + "@babel/helper-split-export-declaration": "7.24.7", + "@babel/plugin-transform-async-generator-functions": "7.26.8", + "@babel/plugin-transform-async-to-generator": "7.25.9", + "@babel/plugin-transform-runtime": "7.26.10", + "@babel/preset-env": "7.26.9", + "@babel/runtime": "7.26.10", + "@discoveryjs/json-ext": "0.5.7", + "@ngtools/webpack": "17.3.17", + "@vitejs/plugin-basic-ssl": "1.1.0", + "ansi-colors": "4.1.3", + "autoprefixer": "10.4.18", + "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", + "fast-glob": "3.3.2", + "http-proxy-middleware": "2.0.8", + "https-proxy-agent": "7.0.4", + "inquirer": "9.2.15", + "jsonc-parser": "3.2.1", + "karma-source-map-support": "1.4.0", + "less": "4.2.0", + "less-loader": "11.1.0", + "license-webpack-plugin": "4.0.2", + "loader-utils": "3.2.1", + "magic-string": "0.30.8", + "mini-css-extract-plugin": "2.8.1", + "mrmime": "2.0.0", + "open": "8.4.2", + "ora": "5.4.1", + "parse5-html-rewriting-stream": "7.0.0", + "picomatch": "4.0.1", + "piscina": "4.4.0", + "postcss": "8.4.35", + "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", + "source-map-loader": "5.0.0", + "source-map-support": "0.5.21", + "terser": "5.29.1", + "tree-kill": "1.2.2", + "tslib": "2.6.2", + "vite": "~5.4.17", + "watchpack": "2.4.0", + "webpack": "5.94.0", + "webpack-dev-middleware": "6.1.2", + "webpack-dev-server": "4.15.1", + "webpack-merge": "5.10.0", + "webpack-subresource-integrity": "5.1.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "optionalDependencies": { + "esbuild": "0.20.1" + }, + "peerDependencies": { + "@angular/compiler-cli": "^17.0.0", + "@angular/localize": "^17.0.0", + "@angular/platform-server": "^17.0.0", + "@angular/service-worker": "^17.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", + "protractor": "^7.0.0", + "tailwindcss": "^2.0.0 || ^3.0.0", + "typescript": ">=5.2 <5.5" + }, + "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/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@angular-devkit/build-webpack": { + "version": "0.1703.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1703.17.tgz", + "integrity": "sha512-81RJe/WFQ1QOJA9du+jK41KaaWXmEWt3frtj9eseWSr+d+Ebt0JMblzM12A70qm7LoUvG48hSiimm7GmkzV3rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/architect": "0.1703.17", + "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" + }, + "peerDependencies": { + "webpack": "^5.30.0", + "webpack-dev-server": "^4.0.0" + } + }, + "node_modules/@angular-devkit/build-webpack/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==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@angular-devkit/core": { + "version": "17.3.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.17.tgz", + "integrity": "sha512-7aNVqS3rOGsSZYAOO44xl2KURwaoOP+EJhJs+LqOGOFpok2kd8YLf4CAMUossMF4H7HsJpgKwYqGrV5eXunrpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.1", + "picomatch": "4.0.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.13.0 || >=20.9.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/core/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==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@angular-devkit/schematics": { + "version": "17.3.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.17.tgz", + "integrity": "sha512-ZXsIJXZm0I0dNu1BqmjfEtQhnzqoupUHHZb4GHm5NeQHBFZctQlkkNxLUU27GVeBUwFgEmP7kFgSLlMPTGSL5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "17.3.17", + "jsonc-parser": "3.2.1", + "magic-string": "0.30.8", + "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/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@angular/animations": { + "version": "17.3.12", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-17.3.12.tgz", + "integrity": "sha512-9hsdWF4gRRcVJtPcCcYLaX1CIyM9wUu6r+xRl6zU5hq8qhl35hig6ounz7CXFAzLf0WDBdM16bPHouVGaG76lg==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/core": "17.3.12" + } + }, + "node_modules/@angular/cdk": { + "version": "17.3.10", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-17.3.10.tgz", + "integrity": "sha512-b1qktT2c1TTTe5nTji/kFAVW92fULK0YhYAvJ+BjZTPKu2FniZNe8o4qqQ0pUuvtMu+ZQxp/QqFYoidIVCjScg==", + "license": "MIT", + "peer": true, + "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.3.17", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-17.3.17.tgz", + "integrity": "sha512-FgOvf9q5d23Cpa7cjP1FYti/v8S1FTm8DEkW3TY8lkkoxh3isu28GFKcLD1p/XF3yqfPkPVHToOFla5QwsEgBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/architect": "0.1703.17", + "@angular-devkit/core": "17.3.17", + "@angular-devkit/schematics": "17.3.17", + "@schematics/angular": "17.3.17", + "@yarnpkg/lockfile": "1.1.0", + "ansi-colors": "4.1.3", + "ini": "4.1.2", + "inquirer": "9.2.15", + "jsonc-parser": "3.2.1", + "npm-package-arg": "11.0.1", + "npm-pick-manifest": "9.0.0", + "open": "8.4.2", + "ora": "5.4.1", + "pacote": "17.0.6", + "resolve": "1.22.8", + "semver": "7.6.0", + "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/common": { + "version": "17.3.12", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-17.3.12.tgz", + "integrity": "sha512-vabJzvrx76XXFrm1RJZ6o/CyG32piTB/1sfFfKHdlH1QrmArb8It4gyk9oEjZ1IkAD0HvBWlfWmn+T6Vx3pdUw==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/core": "17.3.12", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/compiler": { + "version": "17.3.12", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-17.3.12.tgz", + "integrity": "sha512-vwI8oOL/gM+wPnptOVeBbMfZYwzRxQsovojZf+Zol9szl0k3SZ3FycWlxxXZGFu3VIEfrP6pXplTmyODS/Lt1w==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/core": "17.3.12" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + } + } + }, + "node_modules/@angular/compiler-cli": { + "version": "17.3.12", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.3.12.tgz", + "integrity": "sha512-1F8M7nWfChzurb7obbvuE7mJXlHtY1UG58pcwcomVtpPb+kPavgAO8OEvJHYBMV+bzSxkXt5UIwL9lt9jHUxZA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "7.23.9", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^3.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.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/compiler": "17.3.12", + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@angular/core": { + "version": "17.3.12", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-17.3.12.tgz", + "integrity": "sha512-MuFt5yKi161JmauUta4Dh0m8ofwoq6Ino+KoOtkYMBGsSx+A7dSm+DUxxNwdj7+DNyg3LjVGCFgBFnq4g8z06A==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.14.0" + } + }, + "node_modules/@angular/forms": { + "version": "17.3.12", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-17.3.12.tgz", + "integrity": "sha512-tV6r12Q3yEUlXwpVko4E+XscunTIpPkLbaiDn/MTL3Vxi2LZnsLgHyd/i38HaHN+e/H3B0a1ToSOhV5wf3ay4Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/common": "17.3.12", + "@angular/core": "17.3.12", + "@angular/platform-browser": "17.3.12", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/material": { + "version": "17.3.10", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-17.3.10.tgz", + "integrity": "sha512-hHMQES0tQPH5JW33W+mpBPuM8ybsloDTqFPuRV8cboDjosAWfJhzAKF3ozICpNlUrs62La/2Wu/756GcQrxebg==", + "license": "MIT", + "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", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/animations": "^17.0.0 || ^18.0.0", + "@angular/cdk": "17.3.10", + "@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", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/platform-browser": { + "version": "17.3.12", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.12.tgz", + "integrity": "sha512-DYY04ptWh/ulMHzd+y52WCE8QnEYGeIiW3hEIFjCN8z0kbIdFdUtEB0IK5vjNL3ejyhUmphcpeT5PYf3YXtqWQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/animations": "17.3.12", + "@angular/common": "17.3.12", + "@angular/core": "17.3.12" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } + } + }, + "node_modules/@angular/platform-browser-dynamic": { + "version": "17.3.12", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-17.3.12.tgz", + "integrity": "sha512-DQwV7B2x/DRLRDSisngZRdLqHdYbbrqZv2Hmu4ZbnNYaWPC8qvzgE/0CvY+UkDat3nCcsfwsMnlDeB6TL7/IaA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/common": "17.3.12", + "@angular/compiler": "17.3.12", + "@angular/core": "17.3.12", + "@angular/platform-browser": "17.3.12" + } + }, + "node_modules/@angular/router": { + "version": "17.3.12", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-17.3.12.tgz", + "integrity": "sha512-dg7PHBSW9fmPKTVzwvHEeHZPZdpnUqW/U7kj8D29HTP9ur8zZnx9QcnbplwPeYb8yYa62JMnZSEel2X4PxdYBg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/common": "17.3.12", + "@angular/core": "17.3.12", + "@angular/platform-browser": "17.3.12", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "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, + "license": "MIT" + }, + "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, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.10.tgz", + "integrity": "sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.26.10", + "@babel/types": "^7.26.10", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "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, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "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, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "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.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "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, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", + "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.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "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, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "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.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "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, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "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.27.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.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "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, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "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, + "license": "MIT", + "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.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz", + "integrity": "sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-remap-async-to-generator": "^7.25.9", + "@babel/traverse": "^7.26.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", + "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "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.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", + "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "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.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.26.10.tgz", + "integrity": "sha512-NWaL2qG6HRpONTnj4JvDU6th4jYeZOJgu3QhmFTCihib0ermtOJqktA5BduGm3suhhVe9EMP9c9+mfJ/I9slqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.11.0", + "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, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.9.tgz", + "integrity": "sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.8", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.26.0", + "@babel/plugin-syntax-import-attributes": "^7.26.0", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.25.9", + "@babel/plugin-transform-async-generator-functions": "^7.26.8", + "@babel/plugin-transform-async-to-generator": "^7.25.9", + "@babel/plugin-transform-block-scoped-functions": "^7.26.5", + "@babel/plugin-transform-block-scoping": "^7.25.9", + "@babel/plugin-transform-class-properties": "^7.25.9", + "@babel/plugin-transform-class-static-block": "^7.26.0", + "@babel/plugin-transform-classes": "^7.25.9", + "@babel/plugin-transform-computed-properties": "^7.25.9", + "@babel/plugin-transform-destructuring": "^7.25.9", + "@babel/plugin-transform-dotall-regex": "^7.25.9", + "@babel/plugin-transform-duplicate-keys": "^7.25.9", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-dynamic-import": "^7.25.9", + "@babel/plugin-transform-exponentiation-operator": "^7.26.3", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-for-of": "^7.26.9", + "@babel/plugin-transform-function-name": "^7.25.9", + "@babel/plugin-transform-json-strings": "^7.25.9", + "@babel/plugin-transform-literals": "^7.25.9", + "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", + "@babel/plugin-transform-member-expression-literals": "^7.25.9", + "@babel/plugin-transform-modules-amd": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.26.3", + "@babel/plugin-transform-modules-systemjs": "^7.25.9", + "@babel/plugin-transform-modules-umd": "^7.25.9", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-new-target": "^7.25.9", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.26.6", + "@babel/plugin-transform-numeric-separator": "^7.25.9", + "@babel/plugin-transform-object-rest-spread": "^7.25.9", + "@babel/plugin-transform-object-super": "^7.25.9", + "@babel/plugin-transform-optional-catch-binding": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9", + "@babel/plugin-transform-private-methods": "^7.25.9", + "@babel/plugin-transform-private-property-in-object": "^7.25.9", + "@babel/plugin-transform-property-literals": "^7.25.9", + "@babel/plugin-transform-regenerator": "^7.25.9", + "@babel/plugin-transform-regexp-modifiers": "^7.26.0", + "@babel/plugin-transform-reserved-words": "^7.25.9", + "@babel/plugin-transform-shorthand-properties": "^7.25.9", + "@babel/plugin-transform-spread": "^7.25.9", + "@babel/plugin-transform-sticky-regex": "^7.25.9", + "@babel/plugin-transform-template-literals": "^7.26.8", + "@babel/plugin-transform-typeof-symbol": "^7.26.7", + "@babel/plugin-transform-unicode-escapes": "^7.25.9", + "@babel/plugin-transform-unicode-property-regex": "^7.25.9", + "@babel/plugin-transform-unicode-regex": "^7.25.9", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.11.0", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.40.0", + "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, + "license": "ISC", + "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, + "license": "MIT", + "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.26.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", + "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.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==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "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==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "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==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "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==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "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==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "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==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "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==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "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==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "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==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "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==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "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==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "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==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "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==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "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==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "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==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "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==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "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==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "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==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "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==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "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==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "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==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "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==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "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==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "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, + "license": "ISC", + "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.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "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.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "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, + "license": "MIT", + "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/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==", + "dev": true, + "license": "ISC", + "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" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "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, + "license": "MIT" + }, + "node_modules/@ljharb/through": { + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.14.tgz", + "integrity": "sha512-ajBvlKpWucBB17FuQYUShqpqy8GRgYEpJW0vWJbUu1CV9lWyrDCapy0lScU8T8Z6qn49sSwJB3+M+evYIdGg+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8" + }, + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "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_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==", + "license": "MIT", + "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==", + "license": "MIT", + "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" + } + }, + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "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" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "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==", + "license": "MIT", + "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/@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==", + "license": "MIT", + "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/@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==", + "license": "MIT", + "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==", + "license": "MIT", + "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" + } + }, + "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==", + "license": "MIT", + "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" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "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==", + "license": "MIT", + "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/@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==", + "license": "MIT", + "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/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==", + "license": "MIT", + "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/@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==", + "license": "MIT", + "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/@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==", + "license": "MIT", + "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" + } + }, + "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==", + "license": "MIT", + "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/@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==", + "license": "MIT", + "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==", + "license": "MIT", + "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" + } + }, + "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==", + "license": "MIT", + "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/@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==", + "license": "MIT", + "dependencies": { + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "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==", + "license": "MIT", + "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/@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==", + "license": "MIT", + "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/@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==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "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" + } + }, + "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==", + "license": "MIT", + "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/@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==", + "license": "MIT", + "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/@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==", + "license": "MIT", + "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/@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==", + "license": "MIT", + "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/@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==", + "license": "MIT", + "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/@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==", + "license": "MIT", + "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/@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==", + "license": "MIT", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "@material/elevation": "15.0.0-canary.7f224ddd4.0" + } + }, + "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==", + "license": "MIT", + "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/@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==", + "license": "MIT", + "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/@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==", + "license": "MIT", + "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/@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==", + "license": "MIT", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz", + "integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@ngtools/webpack": { + "version": "17.3.17", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-17.3.17.tgz", + "integrity": "sha512-LaO++U8DoqV36M0YLKhubc1+NqM8fyp5DN03k1uP9GvtRchP9+7bfG+IEEZiDFkCUh9lfzi1CiGvUHrN4MYcsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.13.0 || >=20.9.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", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "ISC", + "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, + "license": "ISC" + }, + "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, + "license": "ISC", + "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, + "license": "ISC", + "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/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "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, + "license": "ISC" + }, + "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, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "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, + "license": "ISC", + "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, + "license": "ISC", + "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, + "license": "ISC", + "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, + "license": "ISC", + "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/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "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/package-json/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "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, + "license": "ISC", + "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", + "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "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, + "license": "ISC", + "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, + "license": "ISC", + "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, + "license": "ISC", + "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.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "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, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", + "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "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, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", + "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", + "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.9.2", + "@emnapi/runtime": "1.9.2", + "@napi-rs/wasm-runtime": "^1.1.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz", + "integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", + "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", + "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@schematics/angular": { + "version": "17.3.17", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-17.3.17.tgz", + "integrity": "sha512-S5HwYem5Yjeceb5OLvforNcjfTMh2qsHnTP1BAYL81XPpqeg2udjAkJjKBxCwxMZSqdCMw3ne0eKppEYTaEZ+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "17.3.17", + "@angular-devkit/schematics": "17.3.17", + "jsonc-parser": "3.2.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/@sigstore/bundle": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", + "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", + "dev": true, + "license": "Apache-2.0", + "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, + "license": "Apache-2.0", + "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, + "license": "Apache-2.0", + "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, + "license": "Apache-2.0", + "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/sign/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, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.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, + "license": "Apache-2.0", + "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, + "license": "Apache-2.0", + "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/@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, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@tufjs/models/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express/node_modules/@types/express-serve-static-core": { + "version": "4.19.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.17", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/jasmine": { + "version": "5.1.15", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.15.tgz", + "integrity": "sha512-ZAC8KjmV2MJxbNTrwXFN+HKeajpXQZp6KpPiR6Aa4XvaEnjP6qh23lL/Rqb7AYzlp3h/rcwDrQ7Gg7q28cQTQg==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~7.19.0" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", + "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@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, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "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, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=14.6.0" + }, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.4.tgz", + "integrity": "sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.4", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.4", + "vitest": "4.1.4" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.4.tgz", + "integrity": "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.4.tgz", + "integrity": "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.4.tgz", + "integrity": "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.4", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.4.tgz", + "integrity": "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.4", + "@vitest/utils": "4.1.4", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.4.tgz", + "integrity": "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.4.tgz", + "integrity": "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.4", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils/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, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "Apache-2.0", + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "BSD-3-Clause" + }, + "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, + "license": "Apache-2.0" + }, + "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, + "license": "BSD-2-Clause" + }, + "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, + "license": "ISC", + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "peer": 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, + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "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, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "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, + "license": "MIT" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", + "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "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==", + "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" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001591", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "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, + "license": "MIT", + "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-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, + "license": "BSD-3-Clause", + "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.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", + "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, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", + "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.3", + "core-js-compat": "^3.40.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "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", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "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, + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.18", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.18.tgz", + "integrity": "sha512-VSnGQAOLtP5mib/DPyg2/t+Tlv65NTBz83BJBJvmLVHHuKJVaDOBvJJykiT5TR++em5nfAySPccDZDa4oSrn8A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "dev": true, + "license": "MIT", + "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.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "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" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "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==", + "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" + } + ], + "license": "MIT", + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "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, + "license": "ISC", + "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/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "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, + "license": "ISC" + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "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, + "license": "MIT", + "engines": { + "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, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001787", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001787.tgz", + "integrity": "sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==", + "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" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "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/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "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, + "license": "ISC", + "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, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "ISC" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/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, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/core-js-compat": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", + "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1" + }, + "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, + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", + "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", + "dev": true, + "license": "MIT", + "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/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, + "license": "Python-2.0" + }, + "node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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==", + "dev": true, + "license": "Apache-2.0", + "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, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/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, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "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==", + "dev": true, + "license": "MIT", + "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-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dev": true, + "license": "BSD-2-Clause", + "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.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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, + "license": "BSD-2-Clause", + "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, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "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, + "license": "MIT" + }, + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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" + } + ], + "license": "BSD-2-Clause" + }, + "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, + "license": "BSD-2-Clause", + "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, + "license": "BSD-2-Clause", + "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, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.335", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.335.tgz", + "integrity": "sha512-q9n5T4BR4Xwa2cwbrwcsDJtHD/enpQ5S1xF1IAtdqf5AAgqDFmR/aakqH3ChFdqd/QXJhS3rnnXFtexU7rax6Q==", + "dev": true, + "license": "ISC" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/engine.io": { + "version": "6.6.6", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.6.tgz", + "integrity": "sha512-U2SN0w3OpjFRVlrc17E6TMDmH58Xl9rai1MblNjAdwWp07Kk+llmzX0hjDpQdrDGzwmvOtgM5yI+meYX6iZ2xA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "@types/ws": "^8.5.12", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.18.3" + }, + "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, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", + "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "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, + "license": "MIT", + "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==", + "dev": true, + "license": "BSD-2-Clause", + "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, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.1.tgz", + "integrity": "sha512-OJwEgrpWm/PCMsLVWXKqvcjme3bHNpOgN7Tb6cQnR5n0TPbQx1/Xrn7rqM+wn17bYeT6MGB5sn1Bh5YiGi70nA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "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" + } + }, + "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==", + "dev": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "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, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "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, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "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, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/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, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.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, + "license": "BSD-2-Clause", + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "dev": true, + "license": "MIT", + "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.14.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/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "dev": true, + "license": "MIT", + "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.2", + "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, + "license": "MIT" + }, + "node_modules/express/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "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-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, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "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, + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "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, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "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, + "license": "ISC", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/fs-monkey": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.1.0.tgz", + "integrity": "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==", + "dev": true, + "license": "Unlicense" + }, + "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, + "license": "ISC" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "license": "MIT", + "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, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "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-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, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "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": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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, + "license": "BSD-2-Clause" + }, + "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, + "license": "MIT", + "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/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "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, + "license": "ISC" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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, + "license": "ISC", + "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, + "license": "ISC" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "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, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.8.tgz", + "integrity": "sha512-/iazaeFPmL8KLA6QB7DFAU4O5j+9y/TA0D019MbLtPuFI56VK4BXFzM6j6QS9oGpScy8IIDH4S2LHv3zg/63Bw==", + "dev": true, + "license": "MIT", + "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/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==", + "dev": true, + "license": "MIT", + "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, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "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, + "license": "MIT", + "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, + "license": "ISC", + "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==", + "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" + } + ], + "license": "BSD-3-Clause" + }, + "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, + "license": "MIT", + "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, + "license": "ISC", + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/ignore-walk/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/ignore-walk/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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, + "license": "MIT", + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immutable": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.8.tgz", + "integrity": "sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "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, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "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.", + "dev": true, + "license": "ISC", + "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==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", + "integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==", + "dev": true, + "license": "ISC", + "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, + "license": "MIT", + "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.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", + "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "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==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "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, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "ISC" + }, + "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, + "license": "MIT", + "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, + "license": "BSD-3-Clause", + "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, + "license": "BSD-3-Clause", + "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, + "license": "ISC", + "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, + "license": "BSD-3-Clause", + "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, + "license": "BSD-3-Clause", + "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, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "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, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jasmine-core": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.1.2.tgz", + "integrity": "sha512-2oIUMGn00FdUiqz6epiiJr7xcFyNYj3rDcfmnzfkBnHyBQ3cBQUs4mmyGsOb7TTLb9kxk7dBcmEmqhDKkBoDyA==", + "dev": true, + "license": "MIT", + "peer": 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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "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, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "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" + ], + "license": "MIT" + }, + "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, + "license": "MIT", + "peer": 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, + "license": "MIT", + "dependencies": { + "which": "^1.2.1" + } + }, + "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, + "license": "MIT", + "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-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, + "license": "MIT", + "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, + "license": "MIT", + "peerDependencies": { + "jasmine-core": "^4.0.0 || ^5.0.0", + "karma": "^6.0.0", + "karma-jasmine": "^5.0.0" + } + }, + "node_modules/karma-jasmine/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, + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "source-map-support": "^0.5.5" + } + }, + "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, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.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, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/karma/node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "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, + "license": "MIT", + "engines": { + "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, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/launch-editor": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.13.2.tgz", + "integrity": "sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1", + "shell-quote": "^1.8.3" + } + }, + "node_modules/less": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz", + "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", + "dev": true, + "license": "Apache-2.0", + "peer": 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": "11.1.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-11.1.0.tgz", + "integrity": "sha512-C+uDBV7kS7W5fJlUjq5mPBeBVhYpTIm5gB09APT9o3n/ILeaXVsiSFTbZpTJCJwQ/Crczfn3DmfQFwxYusWFug==", + "dev": true, + "license": "MIT", + "dependencies": { + "klona": "^2.0.4" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "less": "^3.5.0 || ^4.0.0", + "webpack": "^5.0.0" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "ISC", + "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, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.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, + "license": "ISC", + "dependencies": { + "webpack-sources": "^3.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-sources": { + "optional": true + } + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "peer": true, + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "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, + "license": "MIT" + }, + "node_modules/loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "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==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "Apache-2.0", + "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/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, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/magicast": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "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, + "license": "MIT", + "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, + "license": "ISC", + "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/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, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "license": "Unlicense", + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "license": "MIT", + "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, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "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, + "license": "ISC", + "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, + "license": "MIT", + "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.7", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", + "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "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, + "license": "ISC", + "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, + "license": "ISC" + }, + "node_modules/minipass-json-stream": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.2.tgz", + "integrity": "sha512-myxeeTm57lYs8pH2nxPzmEEg8DGIgW+9mv6D4JZD2pa81I/OBjeU7PtICXV6c9eRGTA5JMDsuIPUZRCyBMYNhg==", + "dev": true, + "license": "MIT", + "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, + "license": "ISC", + "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, + "license": "ISC" + }, + "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, + "license": "ISC", + "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, + "license": "ISC", + "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, + "license": "ISC" + }, + "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, + "license": "ISC", + "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, + "license": "ISC", + "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, + "license": "ISC" + }, + "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, + "license": "MIT", + "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, + "license": "ISC", + "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, + "license": "ISC" + }, + "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, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "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, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/needle": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.5.0.tgz", + "integrity": "sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "!win32" + ], + "dependencies": { + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.2" + } + }, + "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, + "license": "MIT", + "optional": true + }, + "node_modules/node-forge": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", + "dev": true, + "license": "(BSD-3-Clause OR GPL-2.0)", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/node-gyp/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "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.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/node-gyp/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/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, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "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, + "license": "ISC", + "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.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "ISC", + "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, + "license": "BSD-2-Clause", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "ISC", + "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, + "license": "BSD-2-Clause", + "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, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.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==", + "dev": true, + "license": "ISC", + "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_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, + "license": "ISC", + "dependencies": { + "ignore-walk": "^6.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "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==", + "dev": true, + "license": "ISC", + "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": "16.2.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-16.2.1.tgz", + "integrity": "sha512-8l+7jxhim55S85fjiDGJ1rZXBWGtRLi1OSb4Z3BPLObPuIaeKRlPRiYMSHU4/81ck3t71Z+UwDDl47gcpmfQQA==", + "dev": true, + "license": "ISC", + "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" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "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==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.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, + "license": "MIT", + "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, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT" + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "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, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "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==", + "dev": true, + "license": "MIT", + "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" + } + }, + "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, + "license": "MIT", + "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/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, + "license": "MIT", + "engines": { + "node": ">=0.10.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==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "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, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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, + "license": "BlueOak-1.0.0" + }, + "node_modules/pacote": { + "version": "17.0.6", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-17.0.6.tgz", + "integrity": "sha512-cJKrW21VRE8vVTRskJo78c/RCvwJCn1f4qgfxL4w77SOWrTCRcmfkYHlHtS0gqpgjv3zhXflRtgsrUCX5xwNnQ==", + "dev": true, + "license": "ISC", + "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.2.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "devOptional": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?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, + "license": "MIT", + "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, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "BlueOak-1.0.0", + "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, + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", + "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/piscina": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.4.0.tgz", + "integrity": "sha512-+AQduEJefrOApE4bV7KRmp3N2JnnyErlVqq4P/jmko4FPz9Z877BCccl/iB3FdrWSUkvbGV9Kan/KllJgat3Vg==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/postcss": { + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "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" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "ISC", + "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, + "license": "MIT", + "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, + "license": "ISC", + "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, + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "ISC", + "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==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "ISC" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "optional": true + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "engines": { + "node": ">=0.9" + } + }, + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "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" + } + ], + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "deprecated": "This package is no longer supported. Please use @npmcli/package-json instead.", + "dev": true, + "license": "ISC", + "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, + "license": "ISC", + "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/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/read-package-json/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "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/read-package-json/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "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", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "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, + "license": "Apache-2.0" + }, + "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, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT" + }, + "node_modules/regex-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.1.tgz", + "integrity": "sha512-yXLRqatcCuKtVHsWrNg0JL3l1zGfdXeEvDa0bdu4tCDQw0RpMDZsqbkyRTUnKMR0tXF627V2oEWjBEaEdqTwtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "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, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.1.tgz", + "integrity": "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "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": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", + "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.124.0", + "@rolldown/pluginutils": "1.0.0-rc.15" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-x64": "1.0.0-rc.15", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" + } + }, + "node_modules/rollup": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", + "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.1", + "@rollup/rollup-android-arm64": "4.60.1", + "@rollup/rollup-darwin-arm64": "4.60.1", + "@rollup/rollup-darwin-x64": "4.60.1", + "@rollup/rollup-freebsd-arm64": "4.60.1", + "@rollup/rollup-freebsd-x64": "4.60.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", + "@rollup/rollup-linux-arm-musleabihf": "4.60.1", + "@rollup/rollup-linux-arm64-gnu": "4.60.1", + "@rollup/rollup-linux-arm64-musl": "4.60.1", + "@rollup/rollup-linux-loong64-gnu": "4.60.1", + "@rollup/rollup-linux-loong64-musl": "4.60.1", + "@rollup/rollup-linux-ppc64-gnu": "4.60.1", + "@rollup/rollup-linux-ppc64-musl": "4.60.1", + "@rollup/rollup-linux-riscv64-gnu": "4.60.1", + "@rollup/rollup-linux-riscv64-musl": "4.60.1", + "@rollup/rollup-linux-s390x-gnu": "4.60.1", + "@rollup/rollup-linux-x64-gnu": "4.60.1", + "@rollup/rollup-linux-x64-musl": "4.60.1", + "@rollup/rollup-openbsd-x64": "4.60.1", + "@rollup/rollup-openharmony-arm64": "4.60.1", + "@rollup/rollup-win32-arm64-msvc": "4.60.1", + "@rollup/rollup-win32-ia32-msvc": "4.60.1", + "@rollup/rollup-win32-x64-gnu": "4.60.1", + "@rollup/rollup-win32-x64-msvc": "4.60.1", + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "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" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "peer": true, + "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==", + "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" + } + ], + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "node_modules/safevalues": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/safevalues/-/safevalues-0.3.4.tgz", + "integrity": "sha512-LRneZZRXNgjzwG4bDQdOTSbze3fHm1EAKN/8bePxnlEZiBmkYEDggaHbuvHI9/hoqHbGfsEA7tWS9GhYHZBBsw==", + "license": "Apache-2.0" + }, + "node_modules/sass": { + "version": "1.71.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", + "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==", + "dev": true, + "license": "MIT", + "peer": 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": "14.1.1", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.1.1.tgz", + "integrity": "sha512-QX8AasDg75monlybel38BZ49JP5Z+uSKfKwF2rO7S74BywaRmGQMUBw9dtkS+ekyM/QnP+NOrRYq8ABMZ9G8jw==", + "dev": true, + "license": "MIT", + "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.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "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/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, + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=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==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "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, + "license": "ISC", + "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, + "license": "ISC" + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "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, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.2.tgz", + "integrity": "sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.8.0", + "mime-types": "~2.1.35", + "parseurl": "~1.3.3" + }, + "engines": { + "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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, + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "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, + "license": "MIT", + "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", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "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.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "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, + "license": "ISC" + }, + "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, + "license": "Apache-2.0", + "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": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socket.io": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", + "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.4.1", + "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.6", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz", + "integrity": "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "~4.4.1", + "ws": "~8.18.3" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", + "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "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, + "license": "MIT", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "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, + "license": "MIT", + "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, + "license": "BSD-3-Clause", + "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, + "license": "BSD-3-Clause", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "BSD-3-Clause", + "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, + "license": "Apache-2.0", + "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, + "license": "CC-BY-3.0" + }, + "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, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "dev": true, + "license": "CC0-1.0" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/ssri": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", + "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/std-env": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "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, + "license": "MIT", + "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": { + "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, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "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, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/tapable": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", + "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "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==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "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, + "license": "ISC", + "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, + "license": "ISC", + "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, + "license": "ISC", + "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, + "license": "MIT", + "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, + "license": "ISC" + }, + "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==", + "dev": true, + "license": "BSD-2-Clause", + "peer": 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.4.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "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/terser-webpack-plugin/node_modules/terser": { + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", + "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "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, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", + "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "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, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "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==", + "license": "0BSD" + }, + "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, + "license": "MIT", + "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-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, + "license": "(MIT OR CC0-1.0)", + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.41", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.41.tgz", + "integrity": "sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg==", + "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" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/undici-types": { + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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, + "license": "ISC", + "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, + "license": "ISC", + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "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" + } + ], + "license": "MIT", + "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, + "license": "BSD-2-Clause", + "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, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "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, + "license": "Apache-2.0", + "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, + "license": "ISC", + "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, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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.9", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", + "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", + "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" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/vitest": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.4.tgz", + "integrity": "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.4", + "@vitest/mocker": "4.1.4", + "@vitest/pretty-format": "4.1.4", + "@vitest/runner": "4.1.4", + "@vitest/snapshot": "4.1.4", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.4", + "@vitest/browser-preview": "4.1.4", + "@vitest/browser-webdriverio": "4.1.4", + "@vitest/coverage-istanbul": "4.1.4", + "@vitest/coverage-v8": "4.1.4", + "@vitest/ui": "4.1.4", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz", + "integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, + "node_modules/vitest/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest/node_modules/postcss": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", + "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", + "dev": true, + "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" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/vitest/node_modules/vite": { + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", + "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.15", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=0.10.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==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "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, + "license": "MIT", + "peer": 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": "6.1.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.1.2.tgz", + "integrity": "sha512-Wu+EHmX326YPYUpQLKmKbTyZZJIB8/n6R09pTmB03kJmnMsVPTo9COzHZFr01txwaCAuZvfBJE4ZCHRcKs5JaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.12", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "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, + "license": "MIT", + "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", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "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", + "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", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.13.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", + "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "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.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "peer": 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, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/webpack/node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "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/webpack/node_modules/watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "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, + "license": "Apache-2.0", + "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, + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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/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, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "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/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "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, + "license": "ISC" + }, + "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, + "license": "MIT", + "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, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "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==", + "license": "MIT", + "peer": true + } + } +} diff --git a/TS/two-inputs/package.json b/TS/two-inputs/package.json index 642045d..5031861 100644 --- a/TS/two-inputs/package.json +++ b/TS/two-inputs/package.json @@ -6,7 +6,9 @@ "start": "ng serve", "build": "ng build", "watch": "ng build --watch --configuration development", - "test": "ng test" + "test": "ng test", + "test:vitest": "vitest run", + "test:coverage": "vitest run --coverage" }, "private": true, "dependencies": { @@ -29,12 +31,14 @@ "@angular/cli": "^17.0.3", "@angular/compiler-cli": "^17.0.0", "@types/jasmine": "~5.1.0", + "@vitest/coverage-v8": "^4.1.4", "jasmine-core": "~5.1.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", - "typescript": "~5.2.2" + "typescript": "~5.2.2", + "vitest": "^4.1.4" } } diff --git a/TS/two-inputs/src/app/app.component.scss b/TS/two-inputs/src/app/app.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/TS/two-inputs/src/app/app.component.test.ts b/TS/two-inputs/src/app/app.component.test.ts new file mode 100644 index 0000000..5f555d3 --- /dev/null +++ b/TS/two-inputs/src/app/app.component.test.ts @@ -0,0 +1,170 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; + +vi.mock("@angular/core", () => ({ + Component: () => (target: unknown) => target, +})); +vi.mock("@angular/common", () => ({ CommonModule: {} })); +vi.mock("@angular/router", () => ({ RouterOutlet: {} })); +vi.mock("@angular/material/input", () => ({ MatInputModule: {} })); +vi.mock("@angular/material/button", () => ({ MatButtonModule: {} })); +vi.mock("@angular/forms", () => ({ FormsModule: {} })); + +vi.mock("./pair-logic", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + findCorrespondingValue: vi.fn(actual.findCorrespondingValue), + findValidPairs: vi.fn(actual.findValidPairs), + changeIndex: vi.fn(actual.changeIndex), + }; +}); + +import { AppComponent } from "./app.component"; +import { findCorrespondingValue } from "./pair-logic"; + +describe("AppComponent", () => { + let comp: AppComponent; + + beforeEach(() => { + vi.mocked(findCorrespondingValue).mockRestore(); + comp = new AppComponent(); + }); + + it("constructor initializes possibleValues with 6 symmetric pairs", () => { + expect(comp.possibleValues).toEqual([ + [100, 225], + [125, 200], + [150, 175], + [175, 150], + [200, 125], + [225, 100], + ]); + }); + + it("ngOnInit recalculates possibleValues", () => { + comp.step = 50; + comp.min = 150; + comp.max = 150; + comp.targetValue = 300; + comp.ngOnInit(); + expect(comp.possibleValues).toEqual([[150, 150]]); + }); + + it("updateInput recalculates possibleValues", () => { + comp.step = 50; + comp.min = 150; + comp.max = 150; + comp.targetValue = 300; + comp.updateInput(); + expect(comp.possibleValues).toEqual([[150, 150]]); + }); + + it("upOne increments indexOne and updates pair", () => { + comp.upOne(); + expect(comp.indexOne).toBe(1); + expect(comp.inputOne).toBe(125); + expect(comp.inputTwo).toBe(200); + }); + + it("downOne wraps to last index and updates pair", () => { + comp.downOne(); + expect(comp.indexOne).toBe(5); + expect(comp.inputOne).toBe(225); + expect(comp.inputTwo).toBe(100); + }); + + it("upTwo increments indexTwo and updates pair", () => { + comp.upTwo(); + expect(comp.indexTwo).toBe(1); + expect(comp.inputTwo).toBe(200); + expect(comp.inputOne).toBe(125); + }); + + it("downTwo wraps to last index and updates pair", () => { + comp.downTwo(); + expect(comp.indexTwo).toBe(5); + expect(comp.inputTwo).toBe(100); + expect(comp.inputOne).toBe(225); + }); + + it("upOne wraps around at end", () => { + comp.indexOne = 5; + comp.upOne(); + expect(comp.indexOne).toBe(0); + }); + + it("downOne wraps around at start", () => { + comp.indexOne = 0; + comp.downOne(); + expect(comp.indexOne).toBe(5); + }); + + it("updateTwoValue does nothing when possibleValues is null", () => { + comp.possibleValues = null; + comp.updateTwoValue(); + expect(comp.inputOne).toBeNull(); + expect(comp.inputTwo).toBeNull(); + }); + + it("updateTwoValue logs error when inputOne is undefined", () => { + const spy = vi.spyOn(console, "error").mockImplementation(() => {}); + comp.possibleValues = [[undefined as unknown as number, 225]]; + comp.indexOne = 0; + comp.updateTwoValue(); + expect(spy).toHaveBeenCalledWith( + "this.inputOne is null or undefined!: ", + undefined, + expect.anything(), + 0, + ); + spy.mockRestore(); + }); + + it("updateTwoValue logs error when findCorrespondingValue returns null", () => { + const spy = vi.spyOn(console, "error").mockImplementation(() => {}); + vi.mocked(findCorrespondingValue).mockReturnValueOnce(null); + comp.possibleValues = [[100, 225]]; + comp.indexOne = 0; + comp.updateTwoValue(); + expect(spy).toHaveBeenCalledWith("result is null!"); + spy.mockRestore(); + }); + + it("updateInput sets possibleValues to null when step is null", () => { + comp.step = null; + const spy = vi.spyOn(console, "error").mockImplementation(() => {}); + comp.updateInput(); + expect(comp.possibleValues).toBeNull(); + spy.mockRestore(); + }); + + it("upTwo skips update when possibleValues becomes null", () => { + comp.step = null; + const spy = vi.spyOn(console, "error").mockImplementation(() => {}); + comp.upTwo(); + expect(comp.possibleValues).toBeNull(); + spy.mockRestore(); + }); + + it("upTwo handles findCorrespondingValue returning null", () => { + vi.mocked(findCorrespondingValue).mockReturnValueOnce(null); + const originalInputOne = comp.inputOne; + comp.upTwo(); + expect(comp.inputOne).toBe(originalInputOne); + }); + + it("downTwo skips update when possibleValues becomes null", () => { + comp.step = null; + const spy = vi.spyOn(console, "error").mockImplementation(() => {}); + comp.downTwo(); + expect(comp.possibleValues).toBeNull(); + spy.mockRestore(); + }); + + it("downTwo handles findCorrespondingValue returning null", () => { + vi.mocked(findCorrespondingValue).mockReturnValueOnce(null); + const originalInputOne = comp.inputOne; + comp.downTwo(); + expect(comp.inputOne).toBe(originalInputOne); + }); +}); diff --git a/TS/two-inputs/src/app/app.component.ts b/TS/two-inputs/src/app/app.component.ts index 337d559..770a441 100644 --- a/TS/two-inputs/src/app/app.component.ts +++ b/TS/two-inputs/src/app/app.component.ts @@ -1,20 +1,31 @@ -import { Component } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { RouterOutlet } from '@angular/router'; -import { MatInputModule } from '@angular/material/input'; -import {MatButtonModule} from '@angular/material/button'; -import { FormsModule } from '@angular/forms'; +import { Component } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { RouterOutlet } from "@angular/router"; +import { MatInputModule } from "@angular/material/input"; +import { MatButtonModule } from "@angular/material/button"; +import { FormsModule } from "@angular/forms"; +import { + findValidPairs, + findCorrespondingValue, + changeIndex, +} from "./pair-logic"; @Component({ - selector: 'app-root', + selector: "app-root", standalone: true, - imports: [CommonModule, RouterOutlet, MatInputModule, FormsModule, MatButtonModule], - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'] + imports: [ + CommonModule, + RouterOutlet, + MatInputModule, + FormsModule, + MatButtonModule, + ], + templateUrl: "./app.component.html", + styleUrls: ["./app.component.scss"], }) export class AppComponent { - inputOne: number | null = null; // Default initialization to 50 - inputTwo: number | null = null; // Default initialization to 50 + inputOne: number | null = null; + inputTwo: number | null = null; min: number | null = 100; max: number | null = 250; step: number | null = 25; @@ -24,115 +35,101 @@ export class AppComponent { possibleValues: Array<[number, number]> | null = []; constructor() { - this.possibleValues = AppComponent.findValidPairs(this.step, this.min, this.max, this.targetValue); + this.possibleValues = findValidPairs( + this.step, + this.min, + this.max, + this.targetValue, + ); } ngOnInit() { - this.possibleValues = AppComponent.findValidPairs(this.step, this.min, this.max, this.targetValue); + this.possibleValues = findValidPairs( + this.step, + this.min, + this.max, + this.targetValue, + ); } public updateInput() { - this.possibleValues = AppComponent.findValidPairs(this.step, this.min, this.max, this.targetValue); + this.possibleValues = findValidPairs( + this.step, + this.min, + this.max, + this.targetValue, + ); } - private changeIndex(currentValue: number, direction: boolean) { - this.possibleValues = AppComponent.findValidPairs(this.step, this.min, this.max, this.targetValue); + private doChangeIndex(currentValue: number, direction: boolean) { + this.possibleValues = findValidPairs( + this.step, + this.min, + this.max, + this.targetValue, + ); const length = this.possibleValues?.length; - if(typeof length !== "undefined") { - if(direction) { - if(currentValue + 1 > length - 1) { - return 0; - } - return currentValue + 1; - } else { - if(currentValue - 1 < 0) { - return length - 1; - } - return currentValue - 1; - } - } else { - console.error(`appComponent, changeIndex, length is undefined!`, length); - } - return currentValue; + return changeIndex(currentValue, direction, length); } updateTwoValue() { - if(this.possibleValues !== null) { + if (this.possibleValues !== null) { this.inputOne = this.possibleValues[this.indexOne][0]; - if(typeof this.inputOne !== "undefined" && this.inputOne !== null) { - const result = AppComponent.findCorrespondingValue(this.possibleValues, this.inputOne); - if(result !== null) { - [this.inputTwo, this.indexTwo] = result; - return; + if (typeof this.inputOne !== "undefined" && this.inputOne !== null) { + const result = findCorrespondingValue( + this.possibleValues, + this.inputOne, + ); + if (result !== null) { + [this.inputTwo, this.indexTwo] = result; + return; + } + console.error("result is null!"); } - console.error(`result is null!`); - } - console.error(`this.inputOne is null or undefined!: `, this.inputOne, this.possibleValues, this.indexOne); + console.error( + "this.inputOne is null or undefined!: ", + this.inputOne, + this.possibleValues, + this.indexOne, + ); } } upOne() { - this.indexOne = this.changeIndex(this.indexOne, true); + this.indexOne = this.doChangeIndex(this.indexOne, true); this.updateTwoValue(); } downOne() { - this.indexOne = this.changeIndex(this.indexOne, false); + this.indexOne = this.doChangeIndex(this.indexOne, false); this.updateTwoValue(); } upTwo() { - this.indexTwo = this.changeIndex(this.indexTwo, true); - if(this.possibleValues !== null) { + this.indexTwo = this.doChangeIndex(this.indexTwo, true); + if (this.possibleValues !== null) { this.inputTwo = this.possibleValues[this.indexTwo][1]; - const result = AppComponent.findCorrespondingValue(this.possibleValues, this.inputTwo); - if(result !== null) { + const result = findCorrespondingValue( + this.possibleValues, + this.inputTwo, + ); + if (result !== null) { [this.inputOne, this.indexOne] = result; } } } downTwo() { - this.indexTwo = this.changeIndex(this.indexTwo, false); - if(this.possibleValues !== null) { + this.indexTwo = this.doChangeIndex(this.indexTwo, false); + if (this.possibleValues !== null) { this.inputTwo = this.possibleValues[this.indexTwo][1]; - const result = AppComponent.findCorrespondingValue(this.possibleValues, this.inputTwo); - if(result !== null) { + const result = findCorrespondingValue( + this.possibleValues, + this.inputTwo, + ); + if (result !== null) { [this.inputOne, this.indexOne] = result; } } } - - private static findCorrespondingValue(pairs: Array<[number, number]>, number: number): [number, number] | null { - for (let index = 0; index < pairs.length; index += 1) { - if (pairs[index][0] === number) { - return [pairs[index][1], index]; // Return n2 if the given number matches n1 - } else if (pairs[index][1] === number) { - return [pairs[index][0], index]; // Return n1 if the given number matches n2 - } - } - console.error("No corresponding value found for the provided number in the pairs.", pairs, number); - return null; // Return null if no matching number is found -} - - private static findValidPairs(x: number | null, y: number | null, z: number | null, ml: number | null): Array<[number, number]> | null { - if (x === null || y === null || z === null || ml === null) { - console.error("findValidPairs, some value is null"); - return null; - } - - const results: Array<[number, number]> = []; - - // Iterate through possible values of n1, which must be multiples of x, at least y, and not more than z - for (let n1 = y; n1 <= ml - y && n1 <= z; n1 += x) { - const n2 = ml - n1; - // Ensure n2 is also a multiple of x, n2 >= y, and n2 <= z - if (n2 % x === 0 && n2 >= y && n2 <= z) { - results.push([n1, n2]); - } - } - - return results; -} - } diff --git a/TS/two-inputs/src/app/pair-logic.test.ts b/TS/two-inputs/src/app/pair-logic.test.ts new file mode 100644 index 0000000..d196f9f --- /dev/null +++ b/TS/two-inputs/src/app/pair-logic.test.ts @@ -0,0 +1,95 @@ +import { describe, it, expect, vi } from "vitest"; +import { findValidPairs, findCorrespondingValue, changeIndex } from "./pair-logic"; + +describe("findValidPairs", () => { + it("returns null when any argument is null", () => { + const spy = vi.spyOn(console, "error").mockImplementation(() => {}); + expect(findValidPairs(null, 100, 250, 325)).toBeNull(); + expect(findValidPairs(25, null, 250, 325)).toBeNull(); + expect(findValidPairs(25, 100, null, 325)).toBeNull(); + expect(findValidPairs(25, 100, 250, null)).toBeNull(); + expect(spy).toHaveBeenCalledTimes(4); + spy.mockRestore(); + }); + + it("returns valid pairs for step=25, min=100, max=250, target=325", () => { + const result = findValidPairs(25, 100, 250, 325); + expect(result).toEqual([ + [100, 225], + [125, 200], + [150, 175], + [175, 150], + [200, 125], + [225, 100], + ]); + }); + + it("returns empty array when no valid pairs exist", () => { + const result = findValidPairs(25, 200, 250, 325); + expect(result).toEqual([]); + }); + + it("returns symmetric pairs", () => { + const result = findValidPairs(50, 100, 200, 300); + expect(result).toEqual([ + [100, 200], + [150, 150], + [200, 100], + ]); + }); + + it("handles case where n2 is not a multiple of step", () => { + const result = findValidPairs(30, 100, 250, 325); + expect(result).toEqual([]); + }); +}); + +describe("findCorrespondingValue", () => { + const pairs: Array<[number, number]> = [ + [100, 225], + [125, 200], + [150, 175], + ]; + + it("returns match on first element", () => { + expect(findCorrespondingValue(pairs, 100)).toEqual([225, 0]); + expect(findCorrespondingValue(pairs, 125)).toEqual([200, 1]); + }); + + it("returns match on second element", () => { + expect(findCorrespondingValue(pairs, 225)).toEqual([100, 0]); + expect(findCorrespondingValue(pairs, 200)).toEqual([125, 1]); + }); + + it("returns null when no match found", () => { + const spy = vi.spyOn(console, "error").mockImplementation(() => {}); + expect(findCorrespondingValue(pairs, 999)).toBeNull(); + expect(spy).toHaveBeenCalledTimes(1); + spy.mockRestore(); + }); +}); + +describe("changeIndex", () => { + it("wraps forward past end", () => { + expect(changeIndex(4, true, 5)).toBe(0); + }); + + it("increments normally forward", () => { + expect(changeIndex(2, true, 5)).toBe(3); + }); + + it("wraps backward past start", () => { + expect(changeIndex(0, false, 5)).toBe(4); + }); + + it("decrements normally backward", () => { + expect(changeIndex(3, false, 5)).toBe(2); + }); + + it("returns currentValue when length is undefined", () => { + const spy = vi.spyOn(console, "error").mockImplementation(() => {}); + expect(changeIndex(3, true, undefined)).toBe(3); + expect(spy).toHaveBeenCalledTimes(1); + spy.mockRestore(); + }); +}); diff --git a/TS/two-inputs/src/app/pair-logic.ts b/TS/two-inputs/src/app/pair-logic.ts new file mode 100644 index 0000000..ce29753 --- /dev/null +++ b/TS/two-inputs/src/app/pair-logic.ts @@ -0,0 +1,67 @@ +export function findValidPairs( + x: number | null, + y: number | null, + z: number | null, + ml: number | null, +): Array<[number, number]> | null { + if (x === null || y === null || z === null || ml === null) { + console.error("findValidPairs, some value is null"); + return null; + } + + const results: Array<[number, number]> = []; + + for (let n1 = y; n1 <= ml - y && n1 <= z; n1 += x) { + const n2 = ml - n1; + if (n2 % x === 0 && n2 >= y && n2 <= z) { + results.push([n1, n2]); + } + } + + return results; +} + +export function findCorrespondingValue( + pairs: Array<[number, number]>, + number: number, +): [number, number] | null { + for (let index = 0; index < pairs.length; index += 1) { + if (pairs[index][0] === number) { + return [pairs[index][1], index]; + } else if (pairs[index][1] === number) { + return [pairs[index][0], index]; + } + } + console.error( + "No corresponding value found for the provided number in the pairs.", + pairs, + number, + ); + return null; +} + +export function changeIndex( + currentValue: number, + direction: boolean, + length: number | undefined, +): number { + if (typeof length !== "undefined") { + if (direction) { + if (currentValue + 1 > length - 1) { + return 0; + } + return currentValue + 1; + } else { + if (currentValue - 1 < 0) { + return length - 1; + } + return currentValue - 1; + } + } else { + console.error( + "appComponent, changeIndex, length is undefined!", + length, + ); + } + return currentValue; +} diff --git a/TS/two-inputs/vitest.config.ts b/TS/two-inputs/vitest.config.ts new file mode 100644 index 0000000..4a60a0a --- /dev/null +++ b/TS/two-inputs/vitest.config.ts @@ -0,0 +1,21 @@ +/// +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, + environment: "node", + include: ["src/**/*.test.ts"], + coverage: { + provider: "v8", + include: ["src/app/**/*.ts"], + exclude: ["**/*.test.ts", "**/*.d.ts"], + thresholds: { + statements: 100, + branches: 100, + functions: 100, + lines: 100, + }, + }, + }, +}); diff --git a/pomodoro_app/analysis_options.yaml b/pomodoro_app/analysis_options.yaml index 0d29021..fe3dc84 100644 --- a/pomodoro_app/analysis_options.yaml +++ b/pomodoro_app/analysis_options.yaml @@ -1,28 +1,177 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml +analyzer: + errors: + missing_return: error + missing_required_param: error + todo: warning + language: + strict-casts: true + strict-inference: true + strict-raw-types: true linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at https://dart.dev/lints. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options + # Error rules + always_use_package_imports: true + avoid_dynamic_calls: true + avoid_slow_async_io: true + avoid_type_to_string: true + avoid_types_as_parameter_names: true + cancel_subscriptions: true + close_sinks: true + literal_only_boolean_expressions: true + no_adjacent_strings_in_list: true + prefer_void_to_null: true + test_types_in_equals: true + throw_in_finally: true + unnecessary_statements: true + # Style rules + always_declare_return_types: true + always_put_required_named_parameters_first: true + annotate_overrides: true + avoid_annotating_with_dynamic: true + avoid_bool_literals_in_conditional_expressions: true + avoid_catching_errors: true + avoid_double_and_int_checks: true + avoid_empty_else: true + avoid_equals_and_hash_code_on_mutable_classes: true + avoid_escaping_inner_quotes: true + avoid_field_initializers_in_const_classes: true + avoid_final_parameters: true + avoid_function_literals_in_foreach_calls: true + avoid_implementing_value_types: true + avoid_init_to_null: true + avoid_multiple_declarations_per_line: true + avoid_positional_boolean_parameters: true + avoid_print: true + avoid_private_typedef_functions: true + avoid_redundant_argument_values: true + avoid_relative_lib_imports: true + avoid_renaming_method_parameters: true + avoid_return_types_on_setters: true + avoid_returning_null_for_void: true + avoid_returning_this: true + avoid_setters_without_getters: true + avoid_shadowing_type_parameters: true + avoid_single_cascade_in_expression_statements: true + avoid_unnecessary_containers: true + avoid_unused_constructor_parameters: true + avoid_void_async: true + cascade_invocations: true + cast_nullable_to_non_nullable: true + combinators_ordering: true + conditional_uri_does_not_exist: true + curly_braces_in_flow_control_structures: true + dangling_library_doc_comments: true + deprecated_consistency: true + directives_ordering: true + empty_catches: true + empty_constructor_bodies: true + eol_at_end_of_file: true + exhaustive_cases: true + file_names: true + hash_and_equals: true + implementation_imports: true + implicit_call_tearoffs: true + join_return_with_assignment: true + leading_newlines_in_multiline_strings: true + library_annotations: true + library_names: true + library_prefixes: true + missing_whitespace_between_adjacent_strings: true + no_default_cases: true + no_leading_underscores_for_library_prefixes: true + no_leading_underscores_for_local_identifiers: true + no_literal_bool_comparisons: true + no_runtimeType_toString: true + non_constant_identifier_names: true + noop_primitive_operations: true + null_check_on_nullable_type_parameter: true + null_closures: true + omit_local_variable_types: true + one_member_abstracts: true + only_throw_errors: true + overridden_fields: true + package_prefixed_library_names: true + parameter_assignments: true + prefer_adjacent_string_concatenation: true + prefer_asserts_in_initializer_lists: true + prefer_collection_literals: true + prefer_conditional_assignment: true + prefer_const_constructors: true + prefer_const_constructors_in_immutables: true + prefer_const_declarations: true + prefer_const_literals_to_create_immutables: true + prefer_constructors_over_static_methods: true + prefer_contains: true + prefer_expression_function_bodies: true + prefer_final_fields: true + prefer_final_in_for_each: true + prefer_final_locals: true + prefer_for_elements_to_map_fromIterable: true + prefer_function_declarations_over_variables: true + prefer_generic_function_type_aliases: true + prefer_if_elements_to_conditional_expressions: true + prefer_if_null_operators: true + prefer_initializing_formals: true + prefer_inlined_adds: true + prefer_int_literals: true + prefer_interpolation_to_compose_strings: true + prefer_is_empty: true + prefer_is_not_empty: true + prefer_is_not_operator: true + prefer_iterable_whereType: true + prefer_null_aware_method_calls: true + prefer_null_aware_operators: true + prefer_single_quotes: true + prefer_spread_collections: true + prefer_typing_uninitialized_variables: true + provide_deprecation_message: true + recursive_getters: true + require_trailing_commas: true + sized_box_for_whitespace: true + slash_for_doc_comments: true + sort_child_properties_last: true + sort_constructors_first: true + sort_unnamed_constructors_first: true + type_annotate_public_apis: true + type_init_formals: true + unawaited_futures: true + unnecessary_await_in_return: true + unnecessary_brace_in_string_interps: true + unnecessary_breaks: true + unnecessary_const: true + unnecessary_constructor_name: true + unnecessary_getters_setters: true + unnecessary_lambdas: true + unnecessary_late: true + unnecessary_library_directive: true + unnecessary_new: true + unnecessary_null_aware_assignments: true + unnecessary_null_aware_operator_on_extension_on_nullable: true + unnecessary_null_checks: true + unnecessary_null_in_if_null_operators: true + unnecessary_nullable_for_final_variable_declarations: true + unnecessary_overrides: true + unnecessary_parenthesis: true + unnecessary_raw_strings: true + unnecessary_string_escapes: true + unnecessary_string_interpolations: true + unnecessary_this: true + unnecessary_to_list_in_spreads: true + unreachable_from_main: true + use_colored_box: true + use_decorated_box: true + use_enums: true + use_full_hex_values_for_flutter_colors: true + use_function_type_syntax_for_parameters: true + use_is_even_rather_than_modulo: true + use_named_constants: true + use_raw_strings: true + use_rethrow_when_possible: true + use_setters_to_change_properties: true + use_string_buffers: true + use_string_in_part_of_directives: true + use_super_parameters: true + use_test_throws_matchers: true + use_to_and_as_if_applicable: true + void_checks: true diff --git a/pomodoro_app/lib/main.dart b/pomodoro_app/lib/main.dart index dfc71b7..c6d8c97 100644 --- a/pomodoro_app/lib/main.dart +++ b/pomodoro_app/lib/main.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'screens/pomodoro_screen.dart'; -import 'theme/pomodoro_theme.dart'; +import 'package:pomodoro_app/screens/pomodoro_screen.dart'; +import 'package:pomodoro_app/theme/pomodoro_theme.dart'; void main() { runApp(const PomodoroApp()); @@ -13,12 +13,10 @@ class PomodoroApp extends StatelessWidget { const PomodoroApp({super.key}); @override - Widget build(BuildContext context) { - return MaterialApp( + Widget build(BuildContext context) => MaterialApp( title: 'Pomodoro', debugShowCheckedModeBanner: false, theme: PomodoroTheme.darkTheme, home: const PomodoroScreen(), ); - } } diff --git a/pomodoro_app/lib/models/pomodoro_state.dart b/pomodoro_app/lib/models/pomodoro_state.dart index 450a0fa..f9e9401 100644 --- a/pomodoro_app/lib/models/pomodoro_state.dart +++ b/pomodoro_app/lib/models/pomodoro_state.dart @@ -1,3 +1,5 @@ +import 'package:meta/meta.dart'; + /// Defines the timer style (technique) the user can choose. enum TimerStyle { /// Classic Pomodoro: 25 min work, 5 min short break, 15 min long break. @@ -88,6 +90,7 @@ extension PomodoroModeLabel on PomodoroMode { } /// Immutable snapshot of the Pomodoro timer state. +@immutable class PomodoroState { /// Creates a [PomodoroState]. const PomodoroState({ @@ -102,8 +105,6 @@ class PomodoroState { /// Creates the default initial state. factory PomodoroState.initial({ int workMinutes = 25, - int shortBreakMinutes = 5, - int longBreakMinutes = 15, int pomodorosPerCycle = 4, }) { final totalSeconds = workMinutes * 60; @@ -137,8 +138,8 @@ class PomodoroState { /// Progress as a value between 0.0 and 1.0. double get progress { - if (totalSeconds == 0) return 1.0; - return 1.0 - (remainingSeconds / totalSeconds); + if (totalSeconds == 0) return 1; + return 1 - (remainingSeconds / totalSeconds); } /// Display label for the current mode, context-aware. @@ -168,16 +169,15 @@ class PomodoroState { bool? isRunning, int? completedPomodoros, int? pomodorosPerCycle, - }) { - return PomodoroState( - mode: mode ?? this.mode, - remainingSeconds: remainingSeconds ?? this.remainingSeconds, - totalSeconds: totalSeconds ?? this.totalSeconds, - isRunning: isRunning ?? this.isRunning, - completedPomodoros: completedPomodoros ?? this.completedPomodoros, - pomodorosPerCycle: pomodorosPerCycle ?? this.pomodorosPerCycle, - ); - } + }) => + PomodoroState( + mode: mode ?? this.mode, + remainingSeconds: remainingSeconds ?? this.remainingSeconds, + totalSeconds: totalSeconds ?? this.totalSeconds, + isRunning: isRunning ?? this.isRunning, + completedPomodoros: completedPomodoros ?? this.completedPomodoros, + pomodorosPerCycle: pomodorosPerCycle ?? this.pomodorosPerCycle, + ); @override bool operator ==(Object other) { @@ -192,8 +192,7 @@ class PomodoroState { } @override - int get hashCode { - return Object.hash( + int get hashCode => Object.hash( mode, remainingSeconds, totalSeconds, @@ -201,5 +200,4 @@ class PomodoroState { completedPomodoros, pomodorosPerCycle, ); - } } diff --git a/pomodoro_app/lib/screens/pomodoro_screen.dart b/pomodoro_app/lib/screens/pomodoro_screen.dart index 828f0a0..2d22de7 100644 --- a/pomodoro_app/lib/screens/pomodoro_screen.dart +++ b/pomodoro_app/lib/screens/pomodoro_screen.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; -import '../models/pomodoro_state.dart'; -import '../services/pomodoro_timer.dart'; -import '../services/notification_service.dart'; -import '../services/sound_service.dart'; -import '../services/sync_service.dart'; -import '../widgets/pomodoro_indicators.dart'; -import '../widgets/timer_controls.dart'; -import '../widgets/timer_display.dart'; +import 'package:pomodoro_app/models/pomodoro_state.dart'; +import 'package:pomodoro_app/services/notification_service.dart'; +import 'package:pomodoro_app/services/pomodoro_timer.dart'; +import 'package:pomodoro_app/services/sound_service.dart'; +import 'package:pomodoro_app/services/sync_service.dart'; +import 'package:pomodoro_app/widgets/pomodoro_indicators.dart'; +import 'package:pomodoro_app/widgets/timer_controls.dart'; +import 'package:pomodoro_app/widgets/timer_display.dart'; /// The main screen of the Pomodoro app. /// @@ -42,7 +42,7 @@ class PomodoroScreenState extends State { if (widget.timer != null) { // Test path: synchronous init, no sync service needed. - _timer = widget.timer!; + _timer = widget.timer; _syncService = widget.syncService; _timer!.addListener(_onTimerChanged); _initialized = true; @@ -54,7 +54,7 @@ class PomodoroScreenState extends State { Future _initAsync() async { _syncService = SyncService( - onStateReceived: _onRemoteState, + onStateReceived: onRemoteState, ); _ownsSyncService = true; await _syncService!.start(); @@ -71,7 +71,9 @@ class PomodoroScreenState extends State { if (mounted) setState(() {}); } - void _onRemoteState(PomodoroState state, String action) { + /// Handles state received from a remote device. + @visibleForTesting + void onRemoteState(PomodoroState state, String action) { _timer?.applyRemoteState(state, action); } diff --git a/pomodoro_app/lib/services/notification_service.dart b/pomodoro_app/lib/services/notification_service.dart index 2476c8a..930ef84 100644 --- a/pomodoro_app/lib/services/notification_service.dart +++ b/pomodoro_app/lib/services/notification_service.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; -import '../models/pomodoro_state.dart'; +import 'package:pomodoro_app/models/pomodoro_state.dart'; /// Sends desktop notifications showing Pomodoro timer status. /// diff --git a/pomodoro_app/lib/services/pomodoro_timer.dart b/pomodoro_app/lib/services/pomodoro_timer.dart index 3008976..9caaa30 100644 --- a/pomodoro_app/lib/services/pomodoro_timer.dart +++ b/pomodoro_app/lib/services/pomodoro_timer.dart @@ -2,10 +2,10 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; -import '../models/pomodoro_state.dart'; -import 'notification_service.dart'; -import 'sound_service.dart'; -import 'sync_service.dart'; +import 'package:pomodoro_app/models/pomodoro_state.dart'; +import 'package:pomodoro_app/services/notification_service.dart'; +import 'package:pomodoro_app/services/sound_service.dart'; +import 'package:pomodoro_app/services/sync_service.dart'; /// Manages the Pomodoro timer logic, independent of UI framework. /// @@ -32,8 +32,6 @@ class PomodoroTimer extends ChangeNotifier { _pomodorosPerCycle = pomodorosPerCycle ?? timerStyle.defaultPomodorosPerCycle; _state = PomodoroState.initial( workMinutes: _workMinutes, - shortBreakMinutes: _shortBreakMinutes, - longBreakMinutes: _longBreakMinutes, pomodorosPerCycle: _pomodorosPerCycle, ); } @@ -84,8 +82,6 @@ class PomodoroTimer extends ChangeNotifier { _pomodorosPerCycle = style.defaultPomodorosPerCycle; _state = PomodoroState.initial( workMinutes: _workMinutes, - shortBreakMinutes: _shortBreakMinutes, - longBreakMinutes: _longBreakMinutes, pomodorosPerCycle: _pomodorosPerCycle, ); _notificationService?.cancel(); diff --git a/pomodoro_app/lib/services/sound_service.dart b/pomodoro_app/lib/services/sound_service.dart index bf48048..eab5171 100644 --- a/pomodoro_app/lib/services/sound_service.dart +++ b/pomodoro_app/lib/services/sound_service.dart @@ -1,7 +1,7 @@ import 'package:audioplayers/audioplayers.dart'; import 'package:flutter/foundation.dart'; -import '../models/pomodoro_state.dart'; +import 'package:pomodoro_app/models/pomodoro_state.dart'; /// Plays notification sounds for Pomodoro timer transitions. /// @@ -16,9 +16,12 @@ class SoundService { /// Pass a custom [playCallback] for testing. SoundService({ @visibleForTesting Future Function(String assetPath)? playCallback, - }) : _playCallback = playCallback; + @visibleForTesting AudioPlayer Function()? playerFactory, + }) : _playCallback = playCallback, + _playerFactory = playerFactory ?? AudioPlayer.new; final Future Function(String assetPath)? _playCallback; + final AudioPlayer Function() _playerFactory; AudioPlayer? _player; bool _disposed = false; @@ -41,8 +44,8 @@ class SoundService { if (_playCallback != null) { await _playCallback(assetPath); } else { - _player?.dispose(); - _player = AudioPlayer(); + await _player?.dispose(); + _player = _playerFactory(); await _player!.play(AssetSource('$_assetPrefix/$assetPath')); } debugPrint('SoundService: Playing $assetPath'); diff --git a/pomodoro_app/lib/services/sync_service.dart b/pomodoro_app/lib/services/sync_service.dart index 7c19861..30a0b28 100644 --- a/pomodoro_app/lib/services/sync_service.dart +++ b/pomodoro_app/lib/services/sync_service.dart @@ -6,7 +6,7 @@ import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import '../models/pomodoro_state.dart'; +import 'package:pomodoro_app/models/pomodoro_state.dart'; /// Callback type for receiving a synced [PomodoroState] and action name. typedef SyncCallback = void Function(PomodoroState state, String action); @@ -26,10 +26,12 @@ class SyncService { required this.onStateReceived, this.port = 41234, @visibleForTesting String? deviceId, + @visibleForTesting bool? isAndroid, @visibleForTesting - Future Function(dynamic host, int port)? + Future Function(Object host, int port)? socketFactory, }) : deviceId = deviceId ?? _generateDeviceId(), + _isAndroid = isAndroid ?? Platform.isAndroid, _socketFactory = socketFactory; /// Unique identifier for this device instance. @@ -45,8 +47,9 @@ class SyncService { /// Called when a state update is received from another device. final SyncCallback onStateReceived; - final Future Function(dynamic host, int port)? + final Future Function(Object host, int port)? _socketFactory; + final bool _isAndroid; RawDatagramSocket? _socket; Timer? _heartbeat; @@ -71,7 +74,6 @@ class SyncService { _socket = await RawDatagramSocket.bind( InternetAddress.anyIPv4, port, - reuseAddress: true, ); } @@ -80,7 +82,6 @@ class SyncService { _socket?.listen( _onSocketEvent, onError: _onError, - cancelOnError: false, ); debugPrint('SyncService: Listening on port $port (device=$deviceId)'); @@ -115,10 +116,13 @@ class SyncService { /// Starts periodic heartbeat that broadcasts current state. /// /// This keeps devices in sync even if an individual message is lost. - void startHeartbeat(PomodoroState Function() stateProvider) { + void startHeartbeat( + PomodoroState Function() stateProvider, { + @visibleForTesting Duration interval = const Duration(seconds: 5), + }) { _heartbeat?.cancel(); _heartbeat = Timer.periodic( - const Duration(seconds: 5), + interval, (_) => broadcast(stateProvider(), 'heartbeat'), ); } @@ -198,8 +202,7 @@ class SyncService { return utf8.encode(jsonEncode(map)); } - static Map _encodeState(PomodoroState state) { - return { + static Map _encodeState(PomodoroState state) => { 'mode': state.mode.name, 'remainingSeconds': state.remainingSeconds, 'totalSeconds': state.totalSeconds, @@ -207,21 +210,19 @@ class SyncService { 'completedPomodoros': state.completedPomodoros, 'pomodorosPerCycle': state.pomodorosPerCycle, }; - } - static PomodoroState _decodeState(Map map) { - return PomodoroState( - mode: PomodoroMode.values.byName(map['mode'] as String), - remainingSeconds: map['remainingSeconds'] as int, - totalSeconds: map['totalSeconds'] as int, - isRunning: map['isRunning'] as bool, - completedPomodoros: map['completedPomodoros'] as int, - pomodorosPerCycle: map['pomodorosPerCycle'] as int, - ); - } + static PomodoroState _decodeState(Map map) => + PomodoroState( + mode: PomodoroMode.values.byName(map['mode'] as String), + remainingSeconds: map['remainingSeconds'] as int, + totalSeconds: map['totalSeconds'] as int, + isRunning: map['isRunning'] as bool, + completedPomodoros: map['completedPomodoros'] as int, + pomodorosPerCycle: map['pomodorosPerCycle'] as int, + ); - static Future _acquireMulticastLock() async { - if (!Platform.isAndroid) return; + Future _acquireMulticastLock() async { + if (!_isAndroid) return; try { await _methodChannel.invokeMethod('acquire'); } on MissingPluginException { @@ -231,8 +232,8 @@ class SyncService { } } - static Future _releaseMulticastLock() async { - if (!Platform.isAndroid) return; + Future _releaseMulticastLock() async { + if (!_isAndroid) return; try { await _methodChannel.invokeMethod('release'); } on MissingPluginException { diff --git a/pomodoro_app/lib/theme/pomodoro_theme.dart b/pomodoro_app/lib/theme/pomodoro_theme.dart index 499a138..d195e0c 100644 --- a/pomodoro_app/lib/theme/pomodoro_theme.dart +++ b/pomodoro_app/lib/theme/pomodoro_theme.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; -import '../models/pomodoro_state.dart'; +import 'package:pomodoro_app/models/pomodoro_state.dart'; /// Provides consistent theming for the Pomodoro app across platforms. -class PomodoroTheme { - PomodoroTheme._(); +abstract final class PomodoroTheme { // Brand colors per mode. static const Color workColor = Color(0xFFE74C3C); @@ -29,8 +28,7 @@ class PomodoroTheme { } /// The app's dark theme. - static ThemeData get darkTheme { - return ThemeData( + static ThemeData get darkTheme => ThemeData( useMaterial3: true, brightness: Brightness.dark, scaffoldBackgroundColor: _darkBackground, @@ -69,5 +67,4 @@ class PomodoroTheme { ), ), ); - } } diff --git a/pomodoro_app/lib/widgets/pomodoro_indicators.dart b/pomodoro_app/lib/widgets/pomodoro_indicators.dart index d938cda..c7b4e2c 100644 --- a/pomodoro_app/lib/widgets/pomodoro_indicators.dart +++ b/pomodoro_app/lib/widgets/pomodoro_indicators.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import '../models/pomodoro_state.dart'; -import '../theme/pomodoro_theme.dart'; +import 'package:pomodoro_app/models/pomodoro_state.dart'; +import 'package:pomodoro_app/theme/pomodoro_theme.dart'; /// Shows completed pomodoro indicators as filled/unfilled dots. class PomodoroIndicators extends StatelessWidget { @@ -15,8 +15,7 @@ class PomodoroIndicators extends StatelessWidget { final PomodoroState state; @override - Widget build(BuildContext context) { - return Row( + Widget build(BuildContext context) => Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate( state.pomodorosPerCycle, @@ -43,5 +42,4 @@ class PomodoroIndicators extends StatelessWidget { }, ), ); - } } diff --git a/pomodoro_app/lib/widgets/timer_controls.dart b/pomodoro_app/lib/widgets/timer_controls.dart index ba369cb..3e128cd 100644 --- a/pomodoro_app/lib/widgets/timer_controls.dart +++ b/pomodoro_app/lib/widgets/timer_controls.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import '../models/pomodoro_state.dart'; -import '../theme/pomodoro_theme.dart'; +import 'package:pomodoro_app/models/pomodoro_state.dart'; +import 'package:pomodoro_app/theme/pomodoro_theme.dart'; /// Row of control buttons for the Pomodoro timer. class TimerControls extends StatelessWidget { diff --git a/pomodoro_app/lib/widgets/timer_display.dart b/pomodoro_app/lib/widgets/timer_display.dart index 7b1b86b..c05f5a7 100644 --- a/pomodoro_app/lib/widgets/timer_display.dart +++ b/pomodoro_app/lib/widgets/timer_display.dart @@ -2,8 +2,8 @@ import 'dart:math'; import 'package:flutter/material.dart'; -import '../models/pomodoro_state.dart'; -import '../theme/pomodoro_theme.dart'; +import 'package:pomodoro_app/models/pomodoro_state.dart'; +import 'package:pomodoro_app/theme/pomodoro_theme.dart'; /// A circular progress indicator that displays the remaining time. class TimerDisplay extends StatelessWidget { @@ -32,7 +32,7 @@ class TimerDisplay extends StatelessWidget { // Background circle. SizedBox.expand( child: CircularProgressIndicator( - value: 1.0, + value: 1, strokeWidth: 8, color: color.withValues(alpha: 0.2), ), diff --git a/pomodoro_app/test/main_test.dart b/pomodoro_app/test/main_test.dart index 6bb411e..dcfe8ff 100644 --- a/pomodoro_app/test/main_test.dart +++ b/pomodoro_app/test/main_test.dart @@ -1,8 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:pomodoro_app/main.dart' as app; import 'package:pomodoro_app/main.dart'; void main() { + testWidgets('main() runs the app', (tester) async { + app.main(); + await tester.pump(); + expect(find.byType(MaterialApp), findsOneWidget); + }); + testWidgets('PomodoroApp builds and shows MaterialApp', (tester) async { await tester.pumpWidget(const PomodoroApp()); expect(find.byType(MaterialApp), findsOneWidget); diff --git a/pomodoro_app/test/models/pomodoro_state_test.dart b/pomodoro_app/test/models/pomodoro_state_test.dart index 46b1750..fa31813 100644 --- a/pomodoro_app/test/models/pomodoro_state_test.dart +++ b/pomodoro_app/test/models/pomodoro_state_test.dart @@ -45,8 +45,6 @@ void main() { test('creates state with custom durations', () { final state = PomodoroState.initial( workMinutes: 30, - shortBreakMinutes: 10, - longBreakMinutes: 20, pomodorosPerCycle: 3, ); expect(state.remainingSeconds, 30 * 60); diff --git a/pomodoro_app/test/screens/pomodoro_screen_test.dart b/pomodoro_app/test/screens/pomodoro_screen_test.dart index c460db4..553c1a1 100644 --- a/pomodoro_app/test/screens/pomodoro_screen_test.dart +++ b/pomodoro_app/test/screens/pomodoro_screen_test.dart @@ -43,8 +43,8 @@ void main() { late FakeTimerController fakeController; Timer fakeTimerFactory(Duration duration, void Function(Timer) callback) { - fakeController = FakeTimerController(); - fakeController._callback = callback; + fakeController = FakeTimerController() + .._callback = callback; return _FakeTimer(fakeController); } @@ -62,11 +62,9 @@ void main() { timer.dispose(); }); - Widget createApp() { - return MaterialApp( + Widget createApp() => MaterialApp( home: PomodoroScreen(timer: timer), ); - } group('PomodoroScreen', () { testWidgets('shows initial time', (tester) async { @@ -132,8 +130,9 @@ void main() { // Start and tick. await tester.tap(find.byIcon(Icons.play_arrow)); await tester.pump(); - fakeController.tick(); - fakeController.tick(); + fakeController + ..tick() + ..tick(); await tester.pump(); // Reset. @@ -203,5 +202,28 @@ void main() { await tester.pump(); expect(find.text('25:00'), findsOneWidget); }); + + testWidgets('onRemoteState delegates to timer', (tester) async { + await tester.pumpWidget(createApp()); + + final state = tester.state( + find.byType(PomodoroScreen), + ); + + const remoteState = PomodoroState( + mode: PomodoroMode.shortBreak, + remainingSeconds: 200, + totalSeconds: 300, + isRunning: false, + completedPomodoros: 2, + pomodorosPerCycle: 4, + ); + + state.onRemoteState(remoteState, 'pause'); + await tester.pump(); + + expect(timer.state.mode, PomodoroMode.shortBreak); + expect(timer.state.remainingSeconds, 200); + }); }); } diff --git a/pomodoro_app/test/services/notification_service_test.dart b/pomodoro_app/test/services/notification_service_test.dart index 908c525..5e696d3 100644 --- a/pomodoro_app/test/services/notification_service_test.dart +++ b/pomodoro_app/test/services/notification_service_test.dart @@ -31,7 +31,7 @@ void main() { }); test('showTimer sends Notify via gdbus', () async { - final state = PomodoroState( + const state = PomodoroState( mode: PomodoroMode.work, remainingSeconds: 1500, totalSeconds: 1500, @@ -53,7 +53,7 @@ void main() { }); test('showTimer shows Start action when paused', () async { - final state = PomodoroState( + const state = PomodoroState( mode: PomodoroMode.shortBreak, remainingSeconds: 120, totalSeconds: 300, @@ -68,7 +68,7 @@ void main() { }); test('showTimer replaces previous notification', () async { - final state = PomodoroState( + const state = PomodoroState( mode: PomodoroMode.work, remainingSeconds: 1500, totalSeconds: 1500, @@ -96,9 +96,8 @@ void main() { test('handles unparsable gdbus output gracefully', () async { final stubService = NotificationService( - runProcess: (exec, args) async { - return ProcessResult(0, 0, 'unexpected output', ''); - }, + runProcess: (exec, args) async => + ProcessResult(0, 0, 'unexpected output', ''), ); final state = PomodoroState.initial(); @@ -192,15 +191,38 @@ void main() { errorService.dispose(); }); + + test('cancel handles CloseNotification error', () async { + final cancelErrorService = NotificationService( + runProcess: (exec, args) async { + if (args.contains( + 'org.freedesktop.Notifications.CloseNotification', + )) { + throw const OSError('close failed'); + } + return ProcessResult(0, 0, '(uint32 42,)', ''); + }, + ); + + final state = PomodoroState.initial(); + await cancelErrorService.showTimer(state: state); + expect(cancelErrorService.currentId, 42); + + // Should not throw; error is caught internally. + await cancelErrorService.cancel(); + expect(cancelErrorService.currentId, 0); + + cancelErrorService.dispose(); + }); }); group('progressBar', () { test('returns empty bar at 0%', () { - expect(NotificationService.progressBar(0.0), '░' * 20); + expect(NotificationService.progressBar(0), '░' * 20); }); test('returns full bar at 100%', () { - expect(NotificationService.progressBar(1.0), '█' * 20); + expect(NotificationService.progressBar(1), '█' * 20); }); test('returns half bar at 50%', () { diff --git a/pomodoro_app/test/services/pomodoro_timer_test.dart b/pomodoro_app/test/services/pomodoro_timer_test.dart index b26ef26..62ad347 100644 --- a/pomodoro_app/test/services/pomodoro_timer_test.dart +++ b/pomodoro_app/test/services/pomodoro_timer_test.dart @@ -1,8 +1,11 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:pomodoro_app/models/pomodoro_state.dart'; +import 'package:pomodoro_app/services/notification_service.dart'; import 'package:pomodoro_app/services/pomodoro_timer.dart'; +import 'package:pomodoro_app/services/sound_service.dart'; /// A controllable fake timer for testing. class FakeTimerController { @@ -41,8 +44,8 @@ void main() { late FakeTimerController fakeController; Timer fakeTimerFactory(Duration duration, void Function(Timer) callback) { - fakeController = FakeTimerController(); - fakeController._callback = callback; + fakeController = FakeTimerController() + .._callback = callback; return _FakeTimer(fakeController); } @@ -86,24 +89,25 @@ void main() { }); test('does nothing if already running', () { - timer.start(); - final stateAfterFirstStart = timer.state; + final stateAfterFirstStart = (timer..start()).state; timer.start(); // second call expect(timer.state, stateAfterFirstStart); }); test('notifies listeners', () { var notified = false; - timer.addListener(() => notified = true); - timer.start(); + timer + ..addListener(() => notified = true) + ..start(); expect(notified, true); }); }); group('pause()', () { test('sets isRunning to false', () { - timer.start(); - timer.pause(); + timer + ..start() + ..pause(); expect(timer.state.isRunning, false); }); @@ -115,8 +119,9 @@ void main() { test('preserves remaining time', () { timer.start(); - fakeController.tick(); // -1s - fakeController.tick(); // -1s + fakeController + ..tick() // -1s + ..tick(); // -1s timer.pause(); expect(timer.state.remainingSeconds, 58); }); @@ -133,8 +138,9 @@ void main() { timer.start(); var count = 0; timer.addListener(() => count++); - fakeController.tick(); - fakeController.tick(); + fakeController + ..tick() + ..tick(); expect(count, 2); }); }); @@ -193,8 +199,9 @@ void main() { group('reset()', () { test('resets to full duration', () { timer.start(); - fakeController.tick(); - fakeController.tick(); + fakeController + ..tick() + ..tick(); timer.reset(); expect(timer.state.remainingSeconds, 60); expect(timer.state.isRunning, false); @@ -219,14 +226,16 @@ void main() { }); test('skips from break to work', () { - timer.skip(); // work -> short break - timer.skip(); // short break -> work + timer + ..skip() // work -> short break + ..skip(); // short break -> work expect(timer.state.mode, PomodoroMode.work); }); test('stops the timer when skipping', () { - timer.start(); - timer.skip(); + timer + ..start() + ..skip(); expect(timer.state.isRunning, false); }); }); @@ -234,15 +243,15 @@ void main() { group('dispose()', () { test('cancels internal timer', () { // Create a separate timer so tearDown does not double-dispose. - final disposableTimer = PomodoroTimer( + PomodoroTimer( workMinutes: 1, shortBreakMinutes: 1, longBreakMinutes: 2, pomodorosPerCycle: 2, timerFactory: fakeTimerFactory, - ); - disposableTimer.start(); - disposableTimer.dispose(); + ) + ..start() + ..dispose(); expect(fakeController.isActive, false); }); }); @@ -259,8 +268,9 @@ void main() { }); test('switches back to pomodoro', () { - timer.switchStyle(TimerStyle.ultraradian); - timer.switchStyle(TimerStyle.pomodoro); + timer + ..switchStyle(TimerStyle.ultraradian) + ..switchStyle(TimerStyle.pomodoro); expect(timer.timerStyle, TimerStyle.pomodoro); expect(timer.state.remainingSeconds, 25 * 60); expect(timer.state.totalSeconds, 25 * 60); @@ -288,8 +298,9 @@ void main() { test('notifies listeners', () { var notified = false; - timer.addListener(() => notified = true); - timer.switchStyle(TimerStyle.ultraradian); + timer + ..addListener(() => notified = true) + ..switchStyle(TimerStyle.ultraradian); expect(notified, true); }); @@ -316,7 +327,7 @@ void main() { var notified = false; timer.addListener(() => notified = true); - final remoteState = PomodoroState( + const remoteState = PomodoroState( mode: PomodoroMode.shortBreak, remainingSeconds: 200, totalSeconds: 300, @@ -334,7 +345,7 @@ void main() { }); test('starts local ticking when remote state is running', () { - final remoteState = PomodoroState( + const remoteState = PomodoroState( mode: PomodoroMode.work, remainingSeconds: 500, totalSeconds: 600, @@ -363,4 +374,109 @@ void main() { expect(timer.state.isRunning, false); }); }); + + group('Session complete with services', () { + late List playedSounds; + late List<_Call> notifyCalls; + late SoundService soundService; + late NotificationService notificationService; + late PomodoroTimer timerWithServices; + + setUp(() { + playedSounds = []; + notifyCalls = []; + + soundService = SoundService( + playCallback: (assetPath) async => playedSounds.add(assetPath), + ); + + notificationService = NotificationService( + runProcess: (exec, args) async { + notifyCalls.add(_Call(exec, args)); + return ProcessResult(0, 0, '(uint32 1,)', ''); + }, + ); + + timerWithServices = PomodoroTimer( + workMinutes: 1, + shortBreakMinutes: 1, + longBreakMinutes: 1, + pomodorosPerCycle: 2, + timerFactory: fakeTimerFactory, + soundService: soundService, + notificationService: notificationService, + ); + }); + + tearDown(() { + timerWithServices.dispose(); + }); + + test('calls sound and notification services on session complete', () { + timerWithServices.start(); + for (var i = 0; i < 60; i++) { + fakeController.tick(); + } + + expect(playedSounds, isNotEmpty); + // showSessionComplete was called (the Notify call after the initial + // showTimer from start). + final sessionCompleteCall = notifyCalls.where( + (c) => c.args.any((a) => a.contains('complete!')), + ); + expect(sessionCompleteCall, isNotEmpty); + }); + + test('long break completes and transitions to work', () { + // Complete 2 work sessions to trigger long break. + timerWithServices.start(); + for (var i = 0; i < 60; i++) { + fakeController.tick(); + } + expect(timerWithServices.state.mode, PomodoroMode.shortBreak); + + timerWithServices.skip(); + expect(timerWithServices.state.mode, PomodoroMode.work); + + timerWithServices.start(); + for (var i = 0; i < 60; i++) { + fakeController.tick(); + } + expect(timerWithServices.state.mode, PomodoroMode.longBreak); + + // Now complete the long break. + timerWithServices.start(); + for (var i = 0; i < 60; i++) { + fakeController.tick(); + } + expect(timerWithServices.state.mode, PomodoroMode.work); + }); + + test('notification updates at 30-second intervals', () { + timerWithServices.start(); + notifyCalls.clear(); + + // Tick 30 times so remainingSeconds goes from 60 to 30. + for (var i = 0; i < 30; i++) { + fakeController.tick(); + } + expect(timerWithServices.state.remainingSeconds, 30); + + // At 30 seconds remaining (divisible by 30), a notification update + // should have been sent. + final timerUpdates = notifyCalls.where( + (c) => c.args.any( + (a) => a.contains('org.freedesktop.Notifications.Notify'), + ), + ); + expect(timerUpdates, isNotEmpty); + }); + }); +} + +/// Captured call for the mock process runner. +class _Call { + _Call(this.executable, this.args); + final String executable; + final List args; } diff --git a/pomodoro_app/test/services/sound_service_test.dart b/pomodoro_app/test/services/sound_service_test.dart index 840fb7e..20a2115 100644 --- a/pomodoro_app/test/services/sound_service_test.dart +++ b/pomodoro_app/test/services/sound_service_test.dart @@ -1,7 +1,34 @@ +import 'package:audioplayers/audioplayers.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pomodoro_app/models/pomodoro_state.dart'; import 'package:pomodoro_app/services/sound_service.dart'; +/// A minimal fake [AudioPlayer] for testing the production path. +/// +/// Uses [implements] + [noSuchMethod] to avoid calling the real +/// AudioPlayer constructor which requires platform bindings. +class _FakeAudioPlayer implements AudioPlayer { + bool playCalled = false; + + @override + Future play( + Source source, { + double? volume, + double? balance, + AudioContext? ctx, + Duration? position, + PlayerMode? mode, + }) async { + playCalled = true; + } + + @override + Future dispose() async {} + + @override + dynamic noSuchMethod(Invocation invocation) => null; +} + void main() { group('SoundService', () { late List playedAssets; @@ -59,5 +86,56 @@ void main() { ); expect(playedAssets, isEmpty); }); + + test('handles playback error gracefully', () async { + final errorService = SoundService( + playCallback: (assetPath) async { + throw Exception('audio error'); + }, + ); + + // Should not throw. + await errorService.playTransitionSound( + completedMode: PomodoroMode.work, + nextMode: PomodoroMode.shortBreak, + ); + + errorService.dispose(); + }); + + test('uses player factory for production path', () async { + final fakePlayer = _FakeAudioPlayer(); + + final factoryService = SoundService( + playerFactory: () => fakePlayer, + ); + + await factoryService.playTransitionSound( + completedMode: PomodoroMode.work, + nextMode: PomodoroMode.shortBreak, + ); + + expect(fakePlayer.playCalled, true); + factoryService.dispose(); + }); + + test('disposes previous player on subsequent plays', () async { + final factoryService = SoundService( + playerFactory: _FakeAudioPlayer.new, + ); + + await factoryService.playTransitionSound( + completedMode: PomodoroMode.work, + nextMode: PomodoroMode.shortBreak, + ); + + // Play again — should dispose the previous player. + await factoryService.playTransitionSound( + completedMode: PomodoroMode.shortBreak, + nextMode: PomodoroMode.work, + ); + + factoryService.dispose(); + }); }); } diff --git a/pomodoro_app/test/services/sync_service_test.dart b/pomodoro_app/test/services/sync_service_test.dart index 69befd3..0d117be 100644 --- a/pomodoro_app/test/services/sync_service_test.dart +++ b/pomodoro_app/test/services/sync_service_test.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pomodoro_app/models/pomodoro_state.dart'; import 'package:pomodoro_app/services/sync_service.dart'; @@ -15,7 +16,7 @@ class FakeDatagramSocket implements RawDatagramSocket { @override int send(List buffer, InternetAddress address, int port) { - sentMessages.add(SentDatagram(buffer, address, port)); + sentMessages.add(SentDatagram(buffer)); return buffer.length; } @@ -25,27 +26,31 @@ class FakeDatagramSocket implements RawDatagramSocket { /// Simulates receiving a datagram. void injectDatagram(List data, InternetAddress address, int port) { _pendingDatagram = Datagram( - data as dynamic, + Uint8List.fromList(data), address, port, ); _controller.add(RawSocketEvent.read); } + /// Simulates a socket error. + void injectError(Object error) { + _controller.addError(error); + } + @override StreamSubscription listen( void Function(RawSocketEvent)? onData, { Function? onError, void Function()? onDone, bool? cancelOnError, - }) { - return _controller.stream.listen( - onData, - onError: onError, - onDone: onDone, - cancelOnError: cancelOnError ?? false, - ); - } + }) => + _controller.stream.listen( + onData, + onError: onError, + onDone: onDone, + cancelOnError: cancelOnError ?? false, + ); @override void joinMulticast(InternetAddress group, [NetworkInterface? interface_]) {} @@ -62,16 +67,16 @@ class FakeDatagramSocket implements RawDatagramSocket { } class SentDatagram { - SentDatagram(this.data, this.address, this.port); + SentDatagram(this.data); final List data; - final InternetAddress address; - final int port; Map get decoded => jsonDecode(utf8.decode(data)) as Map; } void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + group('SyncService', () { late FakeDatagramSocket fakeSocket; late SyncService service; @@ -113,8 +118,9 @@ void main() { final decoded = fakeSocket.sentMessages.first.decoded; expect(decoded['deviceId'], 'test-device-1'); expect(decoded['action'], 'start'); - expect(decoded['state']['mode'], 'work'); - expect(decoded['state']['remainingSeconds'], 25 * 60); + final stateMap = decoded['state'] as Map; + expect(stateMap['mode'], 'work'); + expect(stateMap['remainingSeconds'], 25 * 60); }); test('ignores own messages', () async { @@ -195,12 +201,9 @@ void main() { test('heartbeat sends periodic state', () async { final state = PomodoroState.initial(); - service.startHeartbeat(() => state); - - // Wait for at least one heartbeat interval. - // Note: In tests, Timer.periodic fires based on the test framework. - // We just verify it doesn't crash and can be stopped. - service.stopHeartbeat(); + service + ..startHeartbeat(() => state) + ..stopHeartbeat(); }); }); @@ -256,4 +259,168 @@ void main() { } }); }); + + group('SyncService error paths', () { + test('start catches socket bind failure', () async { + final service = SyncService( + onStateReceived: (_, _) {}, + deviceId: 'err-device', + socketFactory: (host, port) async { + throw const SocketException('bind failed'); + }, + ); + + // Should not throw. + await service.start(); + expect(service.isActive, false); + + await service.dispose(); + }); + + test('broadcast catches send failure', () async { + final throwingSocket = _ThrowingSendSocket(); + final service = SyncService( + onStateReceived: (_, _) {}, + deviceId: 'send-err', + socketFactory: (host, port) async => throwingSocket, + ); + await service.start(); + + // broadcast should not throw. + service.broadcast(PomodoroState.initial(), 'test'); + + await service.dispose(); + }); + + test('heartbeat callback fires and sends broadcast', () async { + final fakeSocket = FakeDatagramSocket(); + final service = SyncService( + onStateReceived: (_, _) {}, + deviceId: 'hb-device', + socketFactory: (host, port) async => fakeSocket, + ); + await service.start(); + + fakeSocket.sentMessages.clear(); + service.startHeartbeat( + PomodoroState.initial, + interval: const Duration(milliseconds: 1), + ); + + // Allow the periodic timer to fire. + await Future.delayed(const Duration(milliseconds: 20)); + + // The heartbeat should have fired at least once. + expect(fakeSocket.sentMessages, isNotEmpty); + + service.stopHeartbeat(); + await service.dispose(); + }); + + test('wake send failure is caught', () async { + // _sendWake is called during start(). If socket.send throws for + // the wake port, it should be caught. + final throwOnWakeSocket = _ThrowingSendSocket(); + final service = SyncService( + onStateReceived: (_, _) {}, + deviceId: 'wake-err', + socketFactory: (host, port) async => throwOnWakeSocket, + ); + + // start() calls _sendWake() which will throw — should be caught. + await service.start(); + + await service.dispose(); + }); + + test('socket stream error invokes onError handler', () async { + final fakeSocket = FakeDatagramSocket(); + final service = SyncService( + onStateReceived: (_, _) {}, + deviceId: 'stream-err', + socketFactory: (host, port) async => fakeSocket, + ); + await service.start(); + + // Inject an error into the stream — should not crash. + fakeSocket.injectError(const SocketException('stream error')); + await Future.delayed(Duration.zero); + + await service.dispose(); + }); + }); + + group('SyncService multicast lock (Android paths)', () { + const channel = MethodChannel('pomodoro_multicast_lock'); + + test('acquires and releases lock when isAndroid is true', () async { + // No handler registered → MissingPluginException caught internally. + final fakeSocket = FakeDatagramSocket(); + final service = SyncService( + onStateReceived: (_, _) {}, + deviceId: 'android-device', + isAndroid: true, + socketFactory: (host, port) async => fakeSocket, + ); + + await service.start(); + await service.dispose(); + }); + + test('handles non-MissingPluginException in acquire', () async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (call) async { + if (call.method == 'acquire') { + throw PlatformException(code: 'ERROR', message: 'lock failed'); + } + return null; + }); + + final fakeSocket = FakeDatagramSocket(); + final service = SyncService( + onStateReceived: (_, _) {}, + deviceId: 'android-err-acquire', + isAndroid: true, + socketFactory: (host, port) async => fakeSocket, + ); + + await service.start(); + await service.dispose(); + + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, null); + }); + + test('handles non-MissingPluginException in release', () async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (call) async { + if (call.method == 'release') { + throw PlatformException(code: 'ERROR', message: 'release failed'); + } + return true; + }); + + final fakeSocket = FakeDatagramSocket(); + final service = SyncService( + onStateReceived: (_, _) {}, + deviceId: 'android-err-release', + isAndroid: true, + socketFactory: (host, port) async => fakeSocket, + ); + + await service.start(); + await service.dispose(); + + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, null); + }); + }); +} + +/// A fake socket that throws on every [send] call. +class _ThrowingSendSocket extends FakeDatagramSocket { + @override + int send(List buffer, InternetAddress address, int port) { + throw const SocketException('send failed'); + } } diff --git a/pomodoro_app/test/theme/pomodoro_theme_test.dart b/pomodoro_app/test/theme/pomodoro_theme_test.dart new file mode 100644 index 0000000..772f529 --- /dev/null +++ b/pomodoro_app/test/theme/pomodoro_theme_test.dart @@ -0,0 +1,14 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:pomodoro_app/models/pomodoro_state.dart'; +import 'package:pomodoro_app/theme/pomodoro_theme.dart'; + +void main() { + group('PomodoroTheme.colorForMode', () { + test('returns longBreakColor for longBreak', () { + expect( + PomodoroTheme.colorForMode(PomodoroMode.longBreak), + PomodoroTheme.longBreakColor, + ); + }); + }); +} diff --git a/python_pkg/fm24_searcher/__init__.py b/python_pkg/fm24_searcher/__init__.py new file mode 100644 index 0000000..0e4e404 --- /dev/null +++ b/python_pkg/fm24_searcher/__init__.py @@ -0,0 +1 @@ +"""FM24 Database Searcher — hybrid binary + HTML player database tool.""" diff --git a/python_pkg/fm24_searcher/__main__.py b/python_pkg/fm24_searcher/__main__.py new file mode 100644 index 0000000..6050114 --- /dev/null +++ b/python_pkg/fm24_searcher/__main__.py @@ -0,0 +1,24 @@ +"""Entry point for FM24 Database Searcher. + +Supports two modes: +- GUI (default): ``python -m python_pkg.fm24_searcher`` +- CLI dump: ``python -m python_pkg.fm24_searcher --dump`` +""" + +from __future__ import annotations + +import sys + +from python_pkg.fm24_searcher.cli import run_dump +from python_pkg.fm24_searcher.gui import main + + +def _main() -> None: + """Dispatch to GUI or CLI based on arguments.""" + if "--dump" in sys.argv: + raise SystemExit(run_dump()) + main() + + +if __name__ == "__main__": + _main() diff --git a/python_pkg/fm24_searcher/binary_parser.py b/python_pkg/fm24_searcher/binary_parser.py new file mode 100644 index 0000000..f5520db --- /dev/null +++ b/python_pkg/fm24_searcher/binary_parser.py @@ -0,0 +1,468 @@ +r"""Binary parser for FM24 database files. + +Extracts player names, DOB, personality bytes from +people_db.dat and save game files. CA/PA require HTML +import; the binary DB does not expose current/potential +ability as readable values. Nationality is stored as a +uint32 at +13 after the name end, not a uint16 at +9. + +File format summary: +- Outer wrapper: 8-byte magic + zstd compressed payload +- Magic: \\x03\\x01tad.\\xef\\r +- Payload: 8-byte inner header + uint32 record_count + records +- Multi-frame files (client_db, server_db, saves): \\x02\\x01fmf. + container with multiple zstd frames +""" + +from __future__ import annotations + +import bisect +import datetime +import re +import struct +from typing import TYPE_CHECKING + +import numpy as np +import zstandard + +from python_pkg.fm24_searcher.models import Player + +if TYPE_CHECKING: + from collections.abc import Callable + from pathlib import Path + +TAD_MAGIC = b"\x03\x01tad.\xef\r" +FMF_MAGIC = b"\x02\x01fmf." +ZSTD_MAGIC = b"\x28\xb5\x2f\xfd" + +# Record separator found between simple records. +REC_SEP = b"\x05\x00\x00\x00\x00" + +MAX_OUTPUT = 500 * 1024 * 1024 # 500 MB decompression limit + +# DOB validation bounds. +_MIN_YEAR = 1930 +_MAX_YEAR = 2012 +_MAX_DAY_OF_YEAR = 366 + +# Name length bounds. +_BOUNDARY_MIN_NAME_LEN = 3 +_EXTRACT_MIN_NAME_LEN = 3 +_MAX_NAME_LEN = 80 + +# Attribute bounds. +_MAX_PERSONALITY_VAL = 20 + +# --- Attribute block constants --- +_ATTR_BLOCK_SIZE = 63 +_ATTR_ZERO_RANGE = range(20, 26) +_ATTR_ZERO_SINGLES = frozenset({40, 41, 42}) +_ATTR_MIN_NONZERO = 30 +_ATTR_SEARCH_WINDOW = 1500 +_SIX_ZEROS = b"\x00\x00\x00\x00\x00\x00" + +# Byte position → attribute name (36 confirmed visible attributes). +ATTR_BLOCK_MAP: dict[int, str] = { + 9: "Crossing", + 10: "Technique", + 11: "Balance", + 12: "Heading", + 13: "Free Kick", + 14: "Marking", + 15: "Off The Ball", + 16: "Vision", + 17: "Decisions", + 18: "Tackling", + 19: "Flair", + 26: "Finishing", + 27: "First Touch", + 29: "Positioning", + 31: "Dribbling", + 32: "Passing", + 36: "Corners", + 37: "Leadership", + 38: "Work Rate", + 39: "Long Throws", + 43: "Anticipation", + 45: "Strength", + 46: "Teamwork", + 47: "Penalty Taking", + 48: "Jumping Reach", + 49: "Long Shots", + 51: "Agility", + 52: "Bravery", + 53: "Composure", + 54: "Aggression", + 55: "Acceleration", + 58: "Stamina", + 59: "Natural Fitness", + 60: "Determination", + 61: "Pace", + 62: "Concentration", +} + + +def _is_valid_attr_block(block: bytes) -> bool: + """Check whether *block* (63 bytes) matches the attribute pattern.""" + if any(b > _MAX_PERSONALITY_VAL for b in block): + return False + if any(block[j] != 0 for j in _ATTR_ZERO_RANGE): + return False + if any(block[j] != 0 for j in _ATTR_ZERO_SINGLES): + return False + return sum(1 for b in block if b > 0) >= _ATTR_MIN_NONZERO + + +def _find_all_attr_blocks(data: bytes) -> tuple[list[int], list[list[int]]]: + """Locate every 63-byte attribute block in *data*. + + Phase 1: collect all candidate block starts at C speed + using ``bytes.find`` on the six-zero anchor at positions + 20-25. Phase 2: validate all candidates at once with + numpy vectorised operations. + + Returns ``(offsets, values)`` where both lists are sorted + by offset and have the same length. + """ + # Phase 1: C-speed scan for the six-zero anchor. + candidates: list[int] = [] + pos = 0 + data_len = len(data) + while True: + idx = data.find(_SIX_ZEROS, pos) + if idx < 0: + break + block_start = idx - 20 + if block_start >= 0 and block_start + _ATTR_BLOCK_SIZE <= data_len: + candidates.append(block_start) + pos = idx + 1 + if not candidates: + return [], [] + + # Phase 2: bulk numpy validation of all candidate blocks. + arr = np.frombuffer(data, dtype=np.uint8) + bs = np.array(candidates, dtype=np.int32) + # sliding_window_view creates a zero-copy view; shape (N-62, 63). + windows = np.lib.stride_tricks.sliding_window_view(arr, _ATTR_BLOCK_SIZE) + # Guard: discard any index beyond the last valid window. + valid_idx = bs[bs < len(windows)] + blocks = windows[valid_idx] # copies only the selected rows + + # All bytes must be <= _MAX_PERSONALITY_VAL (20). + cond1 = (blocks <= _MAX_PERSONALITY_VAL).all(axis=1) + # Positions 40-42 must be zero (positions 20-25 are + # guaranteed zero by the six-zero anchor construction). + cond3 = (blocks[:, [40, 41, 42]] == 0).all(axis=1) + # At least _ATTR_MIN_NONZERO (30) bytes must be non-zero. + cond4 = (blocks > 0).sum(axis=1) >= _ATTR_MIN_NONZERO + + valid_mask = cond1 & cond3 & cond4 + offsets: list[int] = [int(x) for x in valid_idx[valid_mask]] + values: list[list[int]] = [[int(b) for b in row] for row in blocks[valid_mask]] + return offsets, values + + +def _attrs_from_block(block: list[int]) -> dict[str, int]: + """Map a raw 63-byte block to ``{attr_name: value}``.""" + return {name: block[pos] for pos, name in ATTR_BLOCK_MAP.items() if block[pos] > 0} + + +def _enrich_with_attributes( + data: bytes, + players: list[Player], + progress_cb: Callable[[str, int], None] | None = None, +) -> None: + """Find attribute blocks and assign them to nearby players. + + Each player's ``uid`` is its prefix-byte offset in *data*. + The nearest valid block within *_ATTR_SEARCH_WINDOW* bytes + before that offset is picked. + """ + if progress_cb: + progress_cb("Indexing attribute blocks...", 96) + block_offsets, block_values = _find_all_attr_blocks(data) + if not block_offsets: + return + + if progress_cb: + progress_cb( + f"Assigning attributes ({len(block_offsets)} blocks)...", + 97, + ) + for player in players: + idx = bisect.bisect_right(block_offsets, player.uid) - 1 + if idx < 0: + continue + if player.uid - block_offsets[idx] > _ATTR_SEARCH_WINDOW: + continue + player.attributes = _attrs_from_block(block_values[idx]) + + +def _decompress_single(raw: bytes) -> bytes: + """Decompress a TAD-magic .dat file (single zstd frame).""" + if raw[:8] != TAD_MAGIC: + msg = f"Expected TAD magic, got {raw[:8]!r}" + raise ValueError(msg) + dctx = zstandard.ZstdDecompressor() + result: bytes = dctx.decompress(raw[8:], max_output_size=MAX_OUTPUT) + return result + + +def _decompress_multiframe(raw: bytes) -> list[bytes]: + """Decompress a multi-frame FMF container. + + Returns list of decompressed frame payloads. + """ + dctx = zstandard.ZstdDecompressor() + frames: list[bytes] = [] + idx = 0 + while True: + pos = raw.find(ZSTD_MAGIC, idx) + if pos < 0: + break + try: + data = dctx.decompress( + raw[pos:], + max_output_size=MAX_OUTPUT, + ) + frames.append(data) + except zstandard.ZstdError: + pass + idx = pos + 4 + return frames + + +def decompress_file(filepath: Path) -> bytes | list[bytes]: + """Auto-detect format and decompress. + + Single frame → bytes, multi-frame → list[bytes]. + """ + raw = filepath.read_bytes() + if raw[:8] == TAD_MAGIC: + return _decompress_single(raw) + if FMF_MAGIC in raw[:20]: + return _decompress_multiframe(raw) + msg = f"Unknown file format: {filepath}" + raise ValueError(msg) + + +def _dob_from_bytes(data: bytes, offset: int) -> str: + """Extract DOB as ISO string from 4 bytes. + + Format: uint16 day-of-year + uint16 year. + """ + day_of_year = struct.unpack_from(" tuple[str, int, int] | None: + """Find name boundaries from a position in the data. + + Given a name fragment position, find the uint32 length + prefix and return (full_name, start_offset, end_offset). + """ + for back in range(_MAX_NAME_LEN): + off = name_pos - back - 4 + if off < 0: + continue + name_len = struct.unpack_from(" str: + """Try to decode a name at offset with given length. + + Returns the name string if valid, empty string otherwise. + """ + end = offset + length + if end > len(data): + return "" + candidate = data[offset:end] + try: + name = candidate.decode("utf-8") + except UnicodeDecodeError: + return "" + # First and last chars must be alphabetic; names do not + # start or end with punctuation or symbols like '<'. + if not (name[0].isalpha() and name[-1].isalpha()): + return "" + if not all(c.isprintable() or c in " -'." for c in name): + return "" + return name + + +def _try_extract_player( + data: bytes, + prefix_offset: int, +) -> tuple[Player, int] | None: + """Try to extract a player record starting at prefix_offset. + + Returns (Player, name_end_offset) or None if not a valid + record. + """ + if prefix_offset + 30 > len(data): + return None + # Prefix byte should be 0x00. + if data[prefix_offset] != 0x00: + return None + name_len = struct.unpack_from( + " len(data): + return None + + dob = _dob_from_bytes(data, ne) + + # 8 personality bytes at +17 from name end. + personality = list(data[ne + 17 : ne + 25]) + valid_pers = all(0 <= p <= _MAX_PERSONALITY_VAL for p in personality) + + player = Player( + uid=prefix_offset, + name=name, + date_of_birth=dob, + personality=personality if valid_pers else [], + source="binary", + ) + return (player, ne) + + +def _pass1_separator_walk( + data: bytes, + players: list[Player], + seen_offsets: set[int], +) -> None: + """Walk separator-delimited records (short/retired players).""" + idx = 12 + while True: + pos = data.find(REC_SEP, idx) + if pos < 0: + break + prefix_off = pos + 5 + result = _try_extract_player(data, prefix_off) + if result: + player, ne = result + if prefix_off not in seen_offsets: + seen_offsets.add(prefix_off) + players.append(player) + idx = ne + else: + idx = pos + 1 + + +def _pass2_regex_scan( + data: bytes, + players: list[Player], + seen_offsets: set[int], + progress_cb: Callable[[str, int], None] | None = None, +) -> None: + """Scan for name patterns to find active player records.""" + pattern = re.compile( + b"\\x00[\\x02-\\x50]\\x00\\x00\\x00[A-Z\\xc0-\\xff]", + ) + matches = list(pattern.finditer(data)) + total_matches = len(matches) + for i, m in enumerate(matches): + prefix_off = m.start() + if prefix_off in seen_offsets: + continue + result = _try_extract_player(data, prefix_off) + if result: + player, _ne = result + has_dob = bool(player.date_of_birth) + has_multiword = " " in player.name + if (has_dob or has_multiword) and prefix_off not in seen_offsets: + seen_offsets.add(prefix_off) + players.append(player) + if progress_cb and i % 50000 == 0 and total_matches > 0: + pct = 30 + int(65 * i / total_matches) + progress_cb( + f"Scanning... {len(players)} players found", + pct, + ) + + +def parse_people_db( + filepath: Path, + progress_cb: Callable[[str, int], None] | None = None, +) -> list[Player]: + """Parse people_db.dat and extract player records. + + Args: + filepath: Path to people_db.dat. + progress_cb: Optional callback(stage_msg, percent). + + Uses a two-pass approach: + 1. Walk separator-delimited records (short/retired). + 2. Scan for name patterns to find active player records. + """ + if progress_cb: + progress_cb("Decompressing database...", 0) + data = _decompress_single(filepath.read_bytes()) + if progress_cb: + progress_cb("Decompressed, scanning records...", 15) + struct.unpack_from(" list[Player]: + """Simple name-based search.""" + query_lower = query.lower() + return [p for p in players if query_lower in p.name.lower()] diff --git a/python_pkg/fm24_searcher/cli.py b/python_pkg/fm24_searcher/cli.py new file mode 100644 index 0000000..e5e42d1 --- /dev/null +++ b/python_pkg/fm24_searcher/cli.py @@ -0,0 +1,221 @@ +"""CLI dump mode for FM24 Database Searcher. + +Outputs player data as plain text so LLMs can inspect +and verify extracted values. + +Usage:: + + python -m python_pkg.fm24_searcher --dump + python -m python_pkg.fm24_searcher --dump --search Messi + python -m python_pkg.fm24_searcher --dump --limit 20 + python -m python_pkg.fm24_searcher --dump --attrs +""" + +from __future__ import annotations + +import argparse +from pathlib import Path +from typing import TYPE_CHECKING + +from python_pkg.fm24_searcher.binary_parser import ( + parse_people_db, + search_players, +) +from python_pkg.fm24_searcher.models import ALL_VISIBLE_ATTRS, Player + +if TYPE_CHECKING: + from collections.abc import Sequence + +# Default path to FM24 people database. +_DEFAULT_DB = ( + Path.home() + / ".local/share/Steam/steamapps/common" + / "Football Manager 2024/data/database/db" + / "2400/2400_fm/people_db.dat" +) + +_DEFAULT_LIMIT = 50 + + +def _format_player_attrs(player: Player) -> list[str]: + """Format the attributes section for a player.""" + if not player.attributes: + return [" (no attribute block found)"] + lines = [" Attributes:"] + lines += [ + f" {attr}: {val}" + for attr in ALL_VISIBLE_ATTRS + if (val := player.attributes.get(attr, 0)) > 0 + ] + missing = [a for a in ALL_VISIBLE_ATTRS if a not in player.attributes] + if missing: + lines.append(f" Missing attrs: {', '.join(missing)}") + return lines + + +_OPTIONAL_FIELDS = [ + ("DOB", "date_of_birth"), + ("CA", "current_ability"), + ("PA", "potential_ability"), + ("Nationality", "nationality"), + ("Club", "club"), + ("Position", "position"), + ("Personality bytes", "personality"), +] + + +def _format_player(player: Player, *, show_attrs: bool = False) -> str: + """Format one player as a multi-line text block.""" + lines = [f"=== {player.name} ==="] + lines += [ + f" {label}: {getattr(player, field)}" + for label, field in _OPTIONAL_FIELDS + if getattr(player, field) + ] + if show_attrs: + lines.extend(_format_player_attrs(player)) + lines.append(f" Source: {player.source}") + lines.append(f" UID (byte offset): {player.uid}") + return "\n".join(lines) + + +def _format_tsv_header(*, show_attrs: bool) -> str: + """Build TSV header line.""" + cols = ["Name", "DOB", "CA", "PA", "Personality", "UID"] + if show_attrs: + cols.extend(ALL_VISIBLE_ATTRS) + return "\t".join(cols) + + +def _format_tsv_row(player: Player, *, show_attrs: bool) -> str: + """Format one player as a TSV row.""" + cols = [ + player.name, + player.date_of_birth, + str(player.current_ability) if player.current_ability else "", + str(player.potential_ability) if player.potential_ability else "", + ",".join(str(p) for p in player.personality), + str(player.uid), + ] + if show_attrs: + for attr in ALL_VISIBLE_ATTRS: + val = player.attributes.get(attr, 0) + cols.append(str(val) if val > 0 else "") + return "\t".join(cols) + + +def build_parser() -> argparse.ArgumentParser: + """Build the argument parser for CLI mode.""" + parser = argparse.ArgumentParser( + prog="fm24_searcher", + description="FM24 Database Searcher — CLI dump mode", + ) + parser.add_argument( + "--dump", + action="store_true", + help="Enable CLI dump mode (text output, no GUI)", + ) + parser.add_argument( + "--search", + type=str, + default="", + help="Filter players by name substring", + ) + parser.add_argument( + "--limit", + type=int, + default=_DEFAULT_LIMIT, + help=f"Max number of players to show (default {_DEFAULT_LIMIT})", + ) + parser.add_argument( + "--attrs", + action="store_true", + help="Include all visible attributes in output", + ) + parser.add_argument( + "--tsv", + action="store_true", + help="Output as tab-separated values (machine-readable)", + ) + parser.add_argument( + "--db", + type=str, + default="", + help="Path to people_db.dat (overrides default)", + ) + parser.add_argument( + "--with-attrs-only", + action="store_true", + help="Only show players that have attribute blocks", + ) + parser.add_argument( + "--stats", + action="store_true", + help="Show summary statistics about the loaded database", + ) + return parser + + +def _print_stats(players: list[Player]) -> None: + """Print summary statistics about loaded players.""" + total = len(players) + sum(1 for p in players if p.date_of_birth) + with_attrs = sum(1 for p in players if p.attributes) + sum(1 for p in players if p.current_ability) + if with_attrs > 0: + sum(len(p.attributes) for p in players) / with_attrs + if total == 0: + return + if with_attrs > 0: + pass + # Attribute coverage + attr_counts: dict[str, int] = {} + for p in players: + for attr in p.attributes: + attr_counts[attr] = attr_counts.get(attr, 0) + 1 + if attr_counts: + for attr in ALL_VISIBLE_ATTRS: + count = attr_counts.get(attr, 0) + pct = 100 * count / with_attrs if with_attrs else 0 + "*" * int(pct / 5) + + +def run_dump(argv: Sequence[str] | None = None) -> int: + """Execute CLI dump mode. Returns exit code.""" + parser = build_parser() + args = parser.parse_args(argv) + + if not args.dump: + return 1 + + db_path = Path(args.db) if args.db else _DEFAULT_DB + if not db_path.exists(): + return 2 + + def progress(msg: str, pct: int) -> None: + pass + + players = parse_people_db(db_path, progress_cb=progress) + + if args.search: + players = search_players(players, args.search) + + if args.with_attrs_only: + players = [p for p in players if p.attributes] + + if args.stats: + _print_stats(players) + return 0 + + # Apply limit + limited = players[: args.limit] + + # Output + if args.tsv: + for _p in limited: + pass + else: + for _p in limited: + pass + + return 0 diff --git a/python_pkg/fm24_searcher/find_attrs_v2_results.txt b/python_pkg/fm24_searcher/find_attrs_v2_results.txt new file mode 100644 index 0000000..367c0b0 --- /dev/null +++ b/python_pkg/fm24_searcher/find_attrs_v2_results.txt @@ -0,0 +1,71 @@ +Loaded 78 frames + +Frame 4: Messi in records [2897, 63738] +Frame 4: Haaland in records [] + +============================================================ +Frame 3: 83837 records x 196 bytes + + --- Record 2897 in Frame 3 --- + [ 0: 50] [0, 226, 7, 182, 0, 232, 7, 1, 224, 13, 102, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 1, 1, 182, 0, 231, 7, 182, 0, 232, 7, 1, 93, 179, 15, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 1, 6, 182, 0, 231] + [ 50:100] [7, 182, 0, 232, 7, 1, 173, 181, 32, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 1, 8, 182, 0, 231, 7, 182, 0, 232, 7, 1, 174, 217, 7, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 1, 11, 182, 0, 231, 7, 182] + [100:150] [0, 232, 7, 1, 188, 138, 6, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 1, 19, 182, 0, 231, 7, 182, 0, 232, 7, 1, 201, 59, 5, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 1, 4, 182, 0, 231, 7, 182, 0, 232] + [150:196] [7, 1, 120, 21, 13, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 1, 3, 182, 0, 231, 7, 182, 0, 232, 7, 1, 161, 40, 9, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 238, 27, 123, 98, 0, 0] + Attr value matches: 26/196 + + --- Record 63738 in Frame 3 --- + [ 0: 50] [0, 0, 255, 255, 255, 255, 0, 0, 0, 1, 188, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 195, 240, 0, 0, 218, 18, 60, 4, 218, 18, 60, 4, 0, 255, 255, 255, 255, 174, 0, 0, 0, 174, 0, 0, 0, 174, 0, 0, 0] + [ 50:100] [73, 38, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 70, 67, 32, 80, 114, 111, 103, 114, 101, 115, 32, 66, 101, 114, 100, 121, 99, 104, 105, 118, 20, 0, 0, 0, 70, 67, 32, 80, 114, 111, 103, 114, 101, 115, 32, 66] + [100:150] [101, 114, 100, 121, 99, 104, 105, 118, 3, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 108, 7, 1, 0, 108, 7, 1, 0, 108, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + [150:196] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 1, 189, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 196, 240, 0, 0, 231, 18, 60, 4, 231, 18] + Attr value matches: 13/196 + +============================================================ +Frame 28: 84085 records x 104 bytes + + --- Record 2897 in Frame 28 --- + [ 0: 50] [255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 0, 253, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 195, 5, 0, 0, 164, 6, 0, 0, 164, 6, 0, 0, 10] + [ 50:100] [0, 9, 124, 21, 139, 23, 0, 0, 138, 23, 0, 0, 255, 255, 255, 255, 151, 1, 0, 0, 2, 7, 0, 0, 255, 255, 255, 255, 0, 255, 255, 255, 255, 19, 0, 13, 246, 3, 0, 49, 174, 4, 0, 220, 220, 4, 0, 201, 230, 4] + [100:104] [0, 85, 234, 4] + Attr value matches: 12/104 + + --- Record 63738 in Frame 28 --- + [ 0: 50] [19, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 0] + [ 50:100] [226, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 219, 242, 0, 0, 231, 43, 60, 4, 231, 43, 60, 4, 10, 0, 0, 100, 0, 213, 2, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 4, 130, 0, 0, 255, 255, 255] + [100:104] [255, 255, 255, 255] + Attr value matches: 5/104 + +============================================================ +Frame 29: 84085 records x 82 bytes + + --- Record 2897 in Frame 29 --- + [ 0: 50] [81, 11, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 16, 39, 0, 0, 0, 0, 108, 7, 0, 1, 0, 244, 1, 244, 1, 244, 1, 1, 0, 108, 7, 1, 0, 108, 7, 1, 0, 108, 7, 1, 0, 108, 7, 1, 0, 108, 7, 0] + [ 50: 82] [0, 0, 0, 0, 0, 0, 0, 1, 0, 108, 7, 255, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 0] + Attr value matches: 9/82 + + --- Record 63738 in Frame 29 --- + [ 0: 50] [250, 248, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 16, 39, 0, 0, 0, 0, 108, 7, 0, 1, 0, 244, 1, 244, 1, 244, 1, 1, 0, 108, 7, 1, 0, 108, 7, 1, 0, 108, 7, 1, 0, 108, 7, 1, 0, 108, 7, 0] + [ 50: 82] [0, 0, 0, 0, 0, 0, 0, 1, 0, 108, 7, 255, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 0] + Attr value matches: 9/82 + +============================================================ +Cross-reference check: same indices in Frame 3 and 4 + + Frame3[2897] first 30: [0, 226, 7, 182, 0, 232, 7, 1, 224, 13, 102, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 1, 1, 182, 0, 231, 7, 182, 0, 232] + Frame4[2897] first 30: [220, 2, 208, 249, 220, 2, 1, 0, 1, 0, 108, 7, 127, 2, 0, 0, 91, 107, 65, 0, 0, 0, 1, 0, 0, 0, 0, 255, 255, 255] + + Frame3[63738] first 30: [0, 0, 255, 255, 255, 255, 0, 0, 0, 1, 188, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 195, 240, 0, 0, 218, 18, 60, 4, 218] + Frame4[63738] first 30: [255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0] + +============================================================ +Frame 28: 84085 records x 104 bytes + + Frame3[2897] - looking for scaled attributes: + Value 200 at: [] + Value 30 at: [] + x10 matches: 2 - [(152, 120, 12), (177, 40, 4)] + + Frame3[63738] - looking for scaled attributes: + Value 200 at: [] + Value 30 at: [] + x10 matches: 6 - [(64, 70, 7), (67, 80, 8), (78, 100, 10), (88, 70, 7), (91, 80, 8), (102, 100, 10)] diff --git a/python_pkg/fm24_searcher/gui.py b/python_pkg/fm24_searcher/gui.py new file mode 100644 index 0000000..dac6980 --- /dev/null +++ b/python_pkg/fm24_searcher/gui.py @@ -0,0 +1,1164 @@ +"""PyQt6-based GUI for FM24 Database Searcher. + +Provides: +- Player search by name (debounced) +- Attribute filtering (min/max sliders) +- Weighted scouting score +- Player comparison +- Import from binary DB and HTML exports +- Threaded loading with progress overlay +""" + +from __future__ import annotations + +import datetime +from pathlib import Path +import struct +import sys +import threading +import time +from typing import TYPE_CHECKING, ClassVar + +if TYPE_CHECKING: + from collections.abc import Callable + +from PyQt6.QtCore import ( + QAbstractTableModel, + QModelIndex, + QObject, + Qt, + QTimer, + pyqtSignal, +) +from PyQt6.QtGui import QAction, QBrush, QColor, QFont, QPainter +from PyQt6.QtWidgets import ( + QApplication, + QDialog, + QFileDialog, + QGridLayout, + QGroupBox, + QHBoxLayout, + QHeaderView, + QLabel, + QLineEdit, + QMainWindow, + QMessageBox, + QProgressBar, + QPushButton, + QSlider, + QSplitter, + QStatusBar, + QTableView, + QTableWidget, + QTableWidgetItem, + QTabWidget, + QVBoxLayout, + QWidget, +) +import zstandard + +from python_pkg.fm24_searcher.binary_parser import parse_people_db +from python_pkg.fm24_searcher.html_parser import ( + merge_players, + parse_html_export, +) +from python_pkg.fm24_searcher.models import ( + ALL_VISIBLE_ATTRS, + MENTAL_ATTRS, + PHYSICAL_ATTRS, + TECHNICAL_ATTRS, + Player, +) + +# Default path to FM24 people database. +DEFAULT_PEOPLE_DB = ( + Path.home() + / ".local/share/Steam/steamapps/common" + / "Football Manager 2024/data/database/db" + / "2400/2400_fm/people_db.dat" +) + +# Reference date for age calculation. +_TODAY = datetime.datetime.now(tz=datetime.UTC).date() + +# Column layout. +_FIXED_COLS = ["Name", "Age", "CA", "PA"] +_COL_NAME = 0 +_COL_AGE = 1 +_COL_CA = 2 +_COL_PA = 3 + +_FIXED_TOOLTIPS = { + "CA": "Current Ability (1-200)", + "PA": "Potential Ability (1-200)", +} + +# Attribute color thresholds. +_ATTR_EXCELLENT = 18 +_ATTR_GOOD = 15 +_ATTR_AVERAGE = 12 +_ATTR_BELOW = 8 + +_MIN_COMPARE_PLAYERS = 2 +_MIN_ETA_PCT = 5 +_COMPARE_HEADER_ROWS = 4 + +_IMPORT_GUIDE = """\ +

How to Import Player Attributes

+

The FM24 binary database only contains player names and dates of birth. +To see CA, PA, and attribute values, you need to import an +HTML export from Football Manager.

+

Steps:

+
    +
  1. Open Football Manager 2024
  2. +
  3. Go to Scouting > Players (or any player search screen)
  4. +
  5. Set up a custom view with columns: Name, Club, Nat, Pos, CA, PA, + and all attributes (Cor, Cro, Dri, Fin, etc.)
  6. +
  7. Select all players with Ctrl+A
  8. +
  9. Press Ctrl+P to print
  10. +
  11. Choose "Web Page" as the format and save the file
  12. +
  13. Use File > Import HTML Export in this app to load it
  14. +
+

Tip: Export multiple pages (e.g. u21 players, each league) +and import them all — the app merges data automatically.

+""" + + +def _player_age(p: Player) -> int: + """Calculate player age from DOB string.""" + if not p.date_of_birth: + return 0 + try: + dob = datetime.date.fromisoformat(p.date_of_birth) + except ValueError: + return 0 + else: + age = _TODAY.year - dob.year + if (_TODAY.month, _TODAY.day) < (dob.month, dob.day): + age -= 1 + return age + + +def _attr_color(val: int) -> QColor: + """Color-code an attribute value (1-20 scale).""" + if val >= _ATTR_EXCELLENT: + return QColor(0, 150, 50) + if val >= _ATTR_GOOD: + return QColor(80, 180, 80) + if val >= _ATTR_AVERAGE: + return QColor(180, 180, 50) + if val >= _ATTR_BELOW: + return QColor(220, 150, 50) + return QColor(200, 60, 60) + + +def _build_tooltip(p: Player) -> str: + """Build a multi-line tooltip for a player.""" + parts = [p.name] + if p.club: + parts.append(f"Club: {p.club}") + if p.nationality: + parts.append(f"Nationality: {p.nationality}") + if p.position: + parts.append(f"Position: {p.position}") + if p.date_of_birth: + parts.append(f"DOB: {p.date_of_birth}") + if p.value: + parts.append(f"Value: {p.value}") + if p.wage: + parts.append(f"Wage: {p.wage}") + if p.personality: + parts.append(f"Personality: {p.personality}") + return "\n".join(parts) + + +class PlayerTableModel(QAbstractTableModel): + """Virtual model for displaying players efficiently. + + Only renders visible rows, unlike QTableWidget which + creates widget items for every cell in every row. + """ + + def __init__( + self, + parent: QObject | None = None, + ) -> None: + """Initialize the player table model.""" + super().__init__(parent) + self._players: list[Player] = [] + self._ages: list[int] = [] + self._columns = list(_FIXED_COLS) + list( + ALL_VISIBLE_ATTRS, + ) + + def set_players(self, players: list[Player]) -> None: + """Replace all player data.""" + self.beginResetModel() + self._players = players + self._ages = [_player_age(p) for p in players] + self.endResetModel() + + def rowCount( + self, + _parent: QModelIndex = QModelIndex(), + ) -> int: + """Return number of players.""" + return len(self._players) + + def columnCount( + self, + _parent: QModelIndex = QModelIndex(), + ) -> int: + """Return number of columns.""" + return len(self._columns) + + def data( + self, + index: QModelIndex, + role: int = Qt.ItemDataRole.DisplayRole, + ) -> object: + """Return data for the given index and role.""" + if not index.isValid(): + return None + row, col = index.row(), index.column() + if row >= len(self._players): + return None + p = self._players[row] + handlers = { + Qt.ItemDataRole.DisplayRole: lambda: self._display_data(p, row, col), + Qt.ItemDataRole.BackgroundRole: lambda: self._bg_data(p, col), + Qt.ItemDataRole.ToolTipRole: lambda: self._tooltip_data(p, col), + } + if role in handlers: + return handlers[role]() + if role == Qt.ItemDataRole.TextAlignmentRole and col >= 1: + return Qt.AlignmentFlag.AlignCenter + return None + + def _display_data( + self, + p: Player, + row: int, + col: int, + ) -> object: + """Return display text for a cell.""" + if col == _COL_NAME: + return p.name + if col == _COL_AGE: + age = self._ages[row] + return age or "" + if col == _COL_CA: + return p.current_ability or "" + if col == _COL_PA: + return p.potential_ability or "" + fixed_count = len(_FIXED_COLS) + if col >= fixed_count: + attr = self._columns[col] + val = p.get_attr(attr) + return val if val > 0 else "" + return None + + def _bg_data( + self, + p: Player, + col: int, + ) -> QBrush | None: + """Return background brush for attribute cells.""" + fixed_count = len(_FIXED_COLS) + if col >= fixed_count: + attr = self._columns[col] + val = p.get_attr(attr) + if val > 0: + return QBrush(_attr_color(val)) + return None + + def _tooltip_data( + self, + p: Player, + col: int, + ) -> str | None: + """Return tooltip text for a cell.""" + if col == _COL_NAME: + return _build_tooltip(p) + if col == _COL_CA: + return "Current Ability (1-200 scale)" + if col == _COL_PA: + return "Potential Ability (1-200 scale)" + return None + + def headerData( + self, + section: int, + orientation: Qt.Orientation, + role: int = Qt.ItemDataRole.DisplayRole, + ) -> object: + """Return header label or tooltip.""" + if orientation == Qt.Orientation.Horizontal: + if role == Qt.ItemDataRole.DisplayRole: + return self._columns[section] + if role == Qt.ItemDataRole.ToolTipRole: + col_name = self._columns[section] + return _FIXED_TOOLTIPS.get( + col_name, + col_name, + ) + return None + + def _sort_key( + self, + column: int, + ) -> Callable[[int], object] | None: + """Return a sort key function for the column.""" + fixed_count = len(_FIXED_COLS) + if column == _COL_NAME: + return lambda i: self._players[i].name.lower() + if column == _COL_AGE: + return lambda i: self._ages[i] + if column == _COL_CA: + return lambda i: self._players[i].current_ability + if column == _COL_PA: + return lambda i: self._players[i].potential_ability + if column >= fixed_count: + attr = self._columns[column] + return lambda i: self._players[i].get_attr(attr) + return None + + def sort( + self, + column: int, + order: Qt.SortOrder = Qt.SortOrder.AscendingOrder, + ) -> None: + """Sort by column.""" + key_fn = self._sort_key(column) + if key_fn is None: + return + self.beginResetModel() + reverse = order == Qt.SortOrder.DescendingOrder + indices = sorted( + range(len(self._players)), + key=key_fn, + reverse=reverse, + ) + self._players = [self._players[i] for i in indices] + self._ages = [self._ages[i] for i in indices] + self.endResetModel() + + def get_player(self, row: int) -> Player | None: + """Get player at row index.""" + if 0 <= row < len(self._players): + return self._players[row] + return None + + +class LoadingOverlay(QWidget): + """Full-window overlay shown during database loading.""" + + def __init__( + self, + parent: QWidget | None = None, + ) -> None: + """Initialize the loading overlay.""" + super().__init__(parent) + self.setAttribute( + Qt.WidgetAttribute.WA_TransparentForMouseEvents, + on=False, + ) + layout = QVBoxLayout() + layout.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self._title = QLabel("LOADING DATABASE") + title_font = QFont() + title_font.setPointSize(28) + title_font.setBold(True) + self._title.setFont(title_font) + self._title.setAlignment( + Qt.AlignmentFlag.AlignCenter, + ) + self._title.setStyleSheet("color: #333;") + + self._stage = QLabel("Initializing...") + stage_font = QFont() + stage_font.setPointSize(14) + self._stage.setFont(stage_font) + self._stage.setAlignment( + Qt.AlignmentFlag.AlignCenter, + ) + self._stage.setStyleSheet("color: #666;") + + self._progress = QProgressBar() + self._progress.setRange(0, 100) + self._progress.setFixedWidth(500) + self._progress.setFixedHeight(30) + + self._eta = QLabel("") + self._eta.setAlignment( + Qt.AlignmentFlag.AlignCenter, + ) + self._eta.setStyleSheet("color: #888;") + + layout.addWidget(self._title) + layout.addSpacing(20) + layout.addWidget(self._stage) + layout.addSpacing(10) + layout.addWidget( + self._progress, + alignment=Qt.AlignmentFlag.AlignCenter, + ) + layout.addSpacing(5) + layout.addWidget(self._eta) + self.setLayout(layout) + + def update_progress( + self, + stage: str, + percent: int, + eta: str = "", + ) -> None: + """Update displayed loading status.""" + self._stage.setText(stage) + self._progress.setValue(percent) + self._eta.setText(eta) + + def paintEvent(self, event: object) -> None: + """Draw semi-transparent background.""" + painter = QPainter(self) + painter.fillRect( + self.rect(), + QColor(255, 255, 255, 220), + ) + painter.end() + super().paintEvent(event) + + +class FilterPanel(QGroupBox): + """Attribute filter panel with min-value sliders.""" + + def __init__( + self, + title: str, + attrs: list[str], + parent: QWidget | None = None, + ) -> None: + """Initialize filter sliders for given attrs.""" + super().__init__(title, parent) + self.sliders: dict[str, QSlider] = {} + self.labels: dict[str, QLabel] = {} + + layout = QGridLayout() + layout.setSpacing(2) + for i, attr in enumerate(attrs): + lbl = QLabel(attr) + lbl.setFixedWidth(110) + slider = QSlider(Qt.Orientation.Horizontal) + slider.setRange(0, 20) + slider.setValue(0) + val_lbl = QLabel("0") + val_lbl.setFixedWidth(20) + slider.valueChanged.connect( + lambda v, lb=val_lbl: lb.setText(str(v)), + ) + layout.addWidget(lbl, i, 0) + layout.addWidget(slider, i, 1) + layout.addWidget(val_lbl, i, 2) + self.sliders[attr] = slider + self.labels[attr] = val_lbl + self.setLayout(layout) + + def get_filters(self) -> dict[str, int]: + """Return dict of attr_name -> min_value (skip 0).""" + return { + name: slider.value() + for name, slider in self.sliders.items() + if slider.value() > 0 + } + + def reset(self) -> None: + """Reset all sliders to 0.""" + for slider in self.sliders.values(): + slider.setValue(0) + + +class WeightPanel(QGroupBox): + """Weighted scouting formula panel.""" + + def __init__( + self, + parent: QWidget | None = None, + ) -> None: + """Initialize weight sliders for all attributes.""" + super().__init__("Scout Weights", parent) + self.combos: dict[str, QSlider] = {} + layout = QGridLayout() + layout.setSpacing(2) + + for i, attr in enumerate(ALL_VISIBLE_ATTRS): + lbl = QLabel(attr) + lbl.setFixedWidth(110) + slider = QSlider(Qt.Orientation.Horizontal) + slider.setRange(0, 10) + slider.setValue(0) + val_lbl = QLabel("0") + val_lbl.setFixedWidth(20) + slider.valueChanged.connect( + lambda v, lb=val_lbl: lb.setText(str(v)), + ) + layout.addWidget(lbl, i, 0) + layout.addWidget(slider, i, 1) + layout.addWidget(val_lbl, i, 2) + self.combos[attr] = slider + self.setLayout(layout) + + def get_weights(self) -> dict[str, float]: + """Return non-zero weights.""" + return { + name: float(slider.value()) + for name, slider in self.combos.items() + if slider.value() > 0 + } + + +class CompareDialog(QDialog): + """Side-by-side player comparison dialog.""" + + def __init__( + self, + players: list[Player], + parent: QWidget | None = None, + ) -> None: + """Initialize comparison table for given players.""" + super().__init__(parent) + self.setWindowTitle("Compare Players") + self.setMinimumSize(600, 700) + + layout = QVBoxLayout() + table = QTableWidget() + + attrs = ALL_VISIBLE_ATTRS + table.setRowCount( + len(attrs) + _COMPARE_HEADER_ROWS, + ) + table.setColumnCount(len(players) + 1) + + headers = ["Attribute"] + [p.name for p in players] + table.setHorizontalHeaderLabels(headers) + + self._fill_header_rows(table, players) + self._fill_attr_rows(table, players, attrs) + + table.resizeColumnsToContents() + layout.addWidget(table) + self.setLayout(layout) + + @staticmethod + def _fill_header_rows( + table: QTableWidget, + players: list[Player], + ) -> None: + """Populate Name, Age, CA, PA rows.""" + # Name row. + table.setItem(0, 0, QTableWidgetItem("Name")) + for j, p in enumerate(players): + item = QTableWidgetItem(p.name) + font = QFont() + font.setBold(True) + item.setFont(font) + table.setItem(0, j + 1, item) + + # Age row. + table.setItem(1, 0, QTableWidgetItem("Age")) + for j, p in enumerate(players): + item = QTableWidgetItem( + str(_player_age(p)), + ) + table.setItem(1, j + 1, item) + + # CA row. + table.setItem( + _COL_CA, + 0, + QTableWidgetItem("CA"), + ) + for j, p in enumerate(players): + item = QTableWidgetItem() + item.setData( + Qt.ItemDataRole.DisplayRole, + p.current_ability, + ) + table.setItem(_COL_CA, j + 1, item) + + # PA row. + table.setItem( + _COL_PA, + 0, + QTableWidgetItem("PA"), + ) + for j, p in enumerate(players): + item = QTableWidgetItem() + item.setData( + Qt.ItemDataRole.DisplayRole, + p.potential_ability, + ) + table.setItem(_COL_PA, j + 1, item) + + @staticmethod + def _fill_attr_rows( + table: QTableWidget, + players: list[Player], + attrs: tuple[str, ...], + ) -> None: + """Populate attribute comparison rows.""" + for i, attr in enumerate(attrs): + row = i + _COMPARE_HEADER_ROWS + table.setItem( + row, + 0, + QTableWidgetItem(attr), + ) + vals = [p.get_attr(attr) for p in players] + max_val = max(vals) if vals else 0 + for j, p in enumerate(players): + val = p.get_attr(attr) + item = QTableWidgetItem() + item.setData( + Qt.ItemDataRole.DisplayRole, + val, + ) + if val > 0: + item.setBackground(_attr_color(val)) + if val == max_val and len(players) > 1: + font = QFont() + font.setBold(True) + item.setFont(font) + table.setItem(row, j + 1, item) + + +class MainWindow(QMainWindow): + """Main application window.""" + + progress_sig: ClassVar[type] = pyqtSignal(str, int) + done_sig: ClassVar[type] = pyqtSignal(list) + error_sig: ClassVar[type] = pyqtSignal(str) + + def __init__(self) -> None: + """Initialize window, menu, UI, and auto-load.""" + super().__init__() + self.setWindowTitle("FM24 Database Searcher") + self.setMinimumSize(1200, 800) + + self.all_players: list[Player] = [] + self.filtered_players: list[Player] = [] + self._load_thread: threading.Thread | None = None + self._load_start: float = 0.0 + + self._create_menu() + self._create_ui() + self._create_status_bar() + self._create_overlay() + + # Cross-thread signals. + self.progress_sig.connect(self._on_load_progress) + self.done_sig.connect(self._on_load_finished) + self.error_sig.connect(self._on_load_error) + + # Debounce timer for search. + self._search_timer = QTimer() + self._search_timer.setSingleShot(True) + self._search_timer.setInterval(300) + self._search_timer.timeout.connect(self._do_search) + + # Auto-load default DB after the window is shown. + QTimer.singleShot(0, self._auto_load) + + def _create_menu(self) -> None: + menubar = self.menuBar() + if menubar is None: + return + + file_menu = menubar.addMenu("&File") + if file_menu is None: + return + + load_db = QAction("Load &Binary DB...", self) + load_db.triggered.connect(self._load_binary_db) + file_menu.addAction(load_db) + + load_html = QAction( + "Import &HTML Export...", + self, + ) + load_html.triggered.connect(self._load_html) + file_menu.addAction(load_html) + + file_menu.addSeparator() + + quit_action = QAction("&Quit", self) + quit_action.triggered.connect(self.close) + file_menu.addAction(quit_action) + + help_menu = menubar.addMenu("&Help") + if help_menu is None: + return + guide = QAction("How to &Import Attributes...", self) + guide.triggered.connect(self._show_import_guide) + help_menu.addAction(guide) + + def _create_ui(self) -> None: + central = QWidget() + main_layout = QVBoxLayout() + + # Info banner (shown when no attribute data loaded). + self._info_banner = QLabel( + "\u26a0 No attribute data loaded \u2014 " + "use File > Import HTML Export to add CA, PA, " + "and attributes. See Help > How to Import " + "Attributes for instructions.", + ) + self._info_banner.setWordWrap(True) + self._info_banner.setStyleSheet( + "background: #fff3cd; color: #856404; " + "border: 1px solid #ffc107; border-radius: 4px; " + "padding: 8px; font-size: 13px;", + ) + self._info_banner.setVisible(True) + main_layout.addWidget(self._info_banner) + + self._build_search_bar(main_layout) + self._build_meta_filters(main_layout) + self._build_splitter(main_layout) + + apply_btn = QPushButton("Apply Filters") + apply_btn.clicked.connect(self._apply_filters) + main_layout.addWidget(apply_btn) + + central.setLayout(main_layout) + self.setCentralWidget(central) + + def _build_search_bar( + self, + parent_layout: QVBoxLayout, + ) -> None: + search_layout = QHBoxLayout() + self.search_input = QLineEdit() + self.search_input.setPlaceholderText( + "Search by name...", + ) + self.search_input.textChanged.connect( + self._on_search_changed, + ) + search_btn = QPushButton("Search") + search_btn.clicked.connect(self._do_search) + reset_btn = QPushButton("Reset Filters") + reset_btn.clicked.connect(self._reset_filters) + compare_btn = QPushButton("Compare Selected") + compare_btn.clicked.connect( + self._compare_selected, + ) + + search_layout.addWidget(QLabel("Name:")) + search_layout.addWidget( + self.search_input, + stretch=1, + ) + search_layout.addWidget(search_btn) + search_layout.addWidget(reset_btn) + search_layout.addWidget(compare_btn) + parent_layout.addLayout(search_layout) + + def _build_meta_filters( + self, + parent_layout: QVBoxLayout, + ) -> None: + meta_layout = QHBoxLayout() + self.pos_filter = QLineEdit() + self.pos_filter.setPlaceholderText( + "Position filter...", + ) + self.pos_filter.setFixedWidth(120) + self.nat_filter = QLineEdit() + self.nat_filter.setPlaceholderText("Nationality...") + self.nat_filter.setFixedWidth(120) + self.club_filter = QLineEdit() + self.club_filter.setPlaceholderText("Club...") + self.club_filter.setFixedWidth(120) + self.min_ca = QLineEdit() + self.min_ca.setPlaceholderText("Min CA") + self.min_ca.setFixedWidth(60) + + meta_layout.addWidget(QLabel("Pos:")) + meta_layout.addWidget(self.pos_filter) + meta_layout.addWidget(QLabel("Nat:")) + meta_layout.addWidget(self.nat_filter) + meta_layout.addWidget(QLabel("Club:")) + meta_layout.addWidget(self.club_filter) + meta_layout.addWidget(QLabel("Min CA:")) + meta_layout.addWidget(self.min_ca) + meta_layout.addStretch() + parent_layout.addLayout(meta_layout) + + def _build_splitter( + self, + parent_layout: QVBoxLayout, + ) -> None: + splitter = QSplitter(Qt.Orientation.Horizontal) + + filter_tabs = QTabWidget() + self.tech_filter = FilterPanel( + "Technical", + TECHNICAL_ATTRS, + ) + self.mental_filter = FilterPanel( + "Mental", + MENTAL_ATTRS, + ) + self.phys_filter = FilterPanel( + "Physical", + PHYSICAL_ATTRS, + ) + self.weight_panel = WeightPanel() + + filter_tabs.addTab( + self.tech_filter, + "Technical", + ) + filter_tabs.addTab(self.mental_filter, "Mental") + filter_tabs.addTab(self.phys_filter, "Physical") + filter_tabs.addTab( + self.weight_panel, + "Scout Weights", + ) + filter_tabs.setMaximumWidth(350) + splitter.addWidget(filter_tabs) + + self._model = PlayerTableModel() + self.player_table = QTableView() + self.player_table.setModel(self._model) + self.player_table.setAlternatingRowColors(True) + self.player_table.setSelectionBehavior( + QTableView.SelectionBehavior.SelectRows, + ) + self.player_table.setSortingEnabled(True) + hdr = self.player_table.horizontalHeader() + if hdr is not None: + hdr.setStretchLastSection(False) + hdr.setSectionResizeMode( + QHeaderView.ResizeMode.Interactive, + ) + hdr.resizeSection(_COL_NAME, 200) + hdr.resizeSection(_COL_AGE, 45) + hdr.resizeSection(_COL_CA, 45) + hdr.resizeSection(_COL_PA, 45) + fixed_count = len(_FIXED_COLS) + for ci in range( + fixed_count, + len(_FIXED_COLS) + len(ALL_VISIBLE_ATTRS), + ): + hdr.resizeSection(ci, 40) + vhdr = self.player_table.verticalHeader() + if vhdr is not None: + vhdr.setDefaultSectionSize(22) + vhdr.setVisible(False) + splitter.addWidget(self.player_table) + + splitter.setSizes([300, 900]) + parent_layout.addWidget(splitter, stretch=1) + + def _create_status_bar(self) -> None: + self.status = QStatusBar() + self.setStatusBar(self.status) + self.status.showMessage("Loading database...") + + def _create_overlay(self) -> None: + """Create the loading overlay widget.""" + self._overlay = LoadingOverlay(self) + self._overlay.hide() + + def resizeEvent(self, event: object) -> None: + """Keep overlay sized to window.""" + super().resizeEvent(event) + cw = self.centralWidget() + if cw is not None: + self._overlay.setGeometry(cw.geometry()) + + def _show_overlay(self) -> None: + cw = self.centralWidget() + if cw is not None: + self._overlay.setGeometry(cw.geometry()) + self._overlay.update_progress("Starting...", 0) + self._overlay.show() + self._overlay.raise_() + + def _hide_overlay(self) -> None: + self._overlay.hide() + + def _auto_load(self) -> None: + """Auto-load the default people_db.dat.""" + if DEFAULT_PEOPLE_DB.exists(): + self._load_binary_db_from_path( + DEFAULT_PEOPLE_DB, + ) + else: + self.status.showMessage( + "DB not found \u2014 use File > Load Binary DB", + ) + + def _load_binary_db(self) -> None: + filepath, _ = QFileDialog.getOpenFileName( + self, + "Select people_db.dat", + str(Path.home()), + "DAT files (*.dat);;All files (*)", + ) + if not filepath: + return + self._load_binary_db_from_path(Path(filepath)) + + def _load_binary_db_from_path( + self, + filepath: Path, + ) -> None: + """Parse and load a people_db.dat in background.""" + self._show_overlay() + self._load_start = time.monotonic() + + win = self + + def _run() -> None: + try: + players = parse_people_db( + filepath, + progress_cb=win.progress_sig.emit, + ) + win.done_sig.emit(players) + except ( + OSError, + ValueError, + struct.error, + zstandard.ZstdError, + ) as e: + win.error_sig.emit(str(e)) + + self._load_thread = threading.Thread( + target=_run, + daemon=True, + ) + self._load_thread.start() + + def _on_load_progress( + self, + stage: str, + pct: int, + ) -> None: + elapsed = time.monotonic() - self._load_start + if pct > _MIN_ETA_PCT: + est_total = elapsed / (pct / 100) + remaining = est_total - elapsed + eta = f"~{remaining:.0f}s remaining" + else: + eta = "" + self._overlay.update_progress(stage, pct, eta) + + def _on_load_finished( + self, + players: list[Player], + ) -> None: + if self.all_players: + self.all_players = merge_players( + players, + self.all_players, + ) + else: + self.all_players = players + self.filtered_players = self.all_players + self._model.set_players(self.all_players) + self._hide_overlay() + self._update_data_status(len(players)) + + def _on_load_error(self, msg: str) -> None: + self._hide_overlay() + QMessageBox.critical( + self, + "Error", + f"Failed to parse: {msg}", + ) + + def _update_data_status( + self, + loaded_count: int, + ) -> None: + """Update status bar and info banner based on data.""" + has_ca = any(p.current_ability > 0 for p in self.all_players) + has_attrs = any(bool(p.attributes) for p in self.all_players) + self._info_banner.setVisible( + not has_ca and not has_attrs, + ) + total = len(self.all_players) + parts = [f"Loaded {loaded_count:,} players"] + parts.append(f"({total:,} total)") + if has_ca: + ca_count = sum(1 for p in self.all_players if p.current_ability > 0) + parts.append(f"CA: {ca_count:,}") + if has_attrs: + attr_count = sum(1 for p in self.all_players if p.attributes) + parts.append(f"Attrs: {attr_count:,}") + if not has_ca and not has_attrs: + parts.append( + "\u2014 import HTML for attributes", + ) + self.status.showMessage(" | ".join(parts)) + + def _show_import_guide(self) -> None: + """Show the HTML import instructions dialog.""" + QMessageBox.information( + self, + "How to Import Attributes", + _IMPORT_GUIDE, + ) + + def _load_html(self) -> None: + filepath, _ = QFileDialog.getOpenFileName( + self, + "Select FM24 HTML export", + str(Path.home()), + "HTML files (*.html *.htm);;All files (*)", + ) + if not filepath: + return + try: + self.status.showMessage( + "Parsing HTML export...", + ) + QApplication.processEvents() + players = parse_html_export(Path(filepath)) + if self.all_players: + self.all_players = merge_players( + self.all_players, + players, + ) + else: + self.all_players = players + self.filtered_players = self.all_players + self._model.set_players(self.all_players) + self._update_data_status(len(players)) + except ( + OSError, + ValueError, + UnicodeDecodeError, + ) as e: + QMessageBox.critical( + self, + "Error", + f"Failed to parse HTML: {e}", + ) + + def _on_search_changed(self) -> None: + """Restart the debounce timer on text change.""" + self._search_timer.start() + + def _do_search(self) -> None: + """Execute name search (debounced).""" + query = self.search_input.text().strip().lower() + if not query: + self.filtered_players = self.all_players + else: + self.filtered_players = [ + p for p in self.all_players if query in p.name.lower() + ] + self._model.set_players(self.filtered_players) + self.status.showMessage( + f"Showing {len(self.filtered_players):,} players", + ) + + def _apply_filters(self) -> None: + min_attrs: dict[str, int] = {} + min_attrs.update(self.tech_filter.get_filters()) + min_attrs.update( + self.mental_filter.get_filters(), + ) + min_attrs.update(self.phys_filter.get_filters()) + + pos = self.pos_filter.text().strip() + nat = self.nat_filter.text().strip() + club = self.club_filter.text().strip() + ca_text = self.min_ca.text().strip() + min_ca = int(ca_text) if ca_text.isdigit() else None + + query = self.search_input.text().strip().lower() + weights = self.weight_panel.get_weights() + + results: list[tuple[float, Player]] = [] + for p in self.all_players: + if query and query not in p.name.lower(): + continue + if not p.matches_filter( + min_attrs=min_attrs or None, + min_ca=min_ca, + position_filter=pos or None, + nationality_filter=nat or None, + club_filter=club or None, + ): + continue + score = p.weighted_score(weights) if weights else 0.0 + results.append((score, p)) + + if weights: + results.sort( + key=lambda x: x[0], + reverse=True, + ) + else: + results.sort( + key=lambda x: x[1].current_ability, + reverse=True, + ) + + self.filtered_players = [p for _, p in results] + self._model.set_players(self.filtered_players) + self.status.showMessage( + f"Filtered: {len(self.filtered_players):,} players", + ) + + def _reset_filters(self) -> None: + self.tech_filter.reset() + self.mental_filter.reset() + self.phys_filter.reset() + self.search_input.clear() + self.pos_filter.clear() + self.nat_filter.clear() + self.club_filter.clear() + self.min_ca.clear() + self.filtered_players = self.all_players + self._model.set_players(self.all_players) + self.status.showMessage("Filters reset") + + def _compare_selected(self) -> None: + sel = self.player_table.selectionModel() + if sel is None: + return + indexes = sel.selectedRows() + if len(indexes) < _MIN_COMPARE_PLAYERS: + QMessageBox.information( + self, + "Compare", + "Select at least 2 rows to compare.", + ) + return + players = [] + for idx in sorted( + indexes, + key=lambda i: i.row(), + ): + p = self._model.get_player(idx.row()) + if p: + players.append(p) + if len(players) >= _MIN_COMPARE_PLAYERS: + dlg = CompareDialog(players, self) + dlg.exec() + + +def main() -> None: + """Launch the FM24 Database Searcher GUI.""" + app = QApplication(sys.argv) + app.setApplicationName("FM24 Database Searcher") + window = MainWindow() + window.show() + sys.exit(app.exec()) diff --git a/python_pkg/fm24_searcher/html_parser.py b/python_pkg/fm24_searcher/html_parser.py new file mode 100644 index 0000000..966e3ab --- /dev/null +++ b/python_pkg/fm24_searcher/html_parser.py @@ -0,0 +1,311 @@ +"""HTML import parser for FM24 exported views. + +FM24 allows exporting search/scout views via Ctrl+P (Printing). +The result is an HTML file containing player data in tables. +This module parses that HTML to extract player attributes. + +Supported: the default FM24 HTML export format with + containing player rows and attribute columns. +""" + +from __future__ import annotations + +import contextlib +from dataclasses import dataclass, field +import html +import re +from typing import TYPE_CHECKING + +from python_pkg.fm24_searcher.models import ALL_VISIBLE_ATTRS, GOALKEEPER_ATTRS, Player + +if TYPE_CHECKING: + from pathlib import Path + +# Common FM attribute header normalizations. +_HEADER_MAP: dict[str, str] = { + "cor": "Corners", + "cro": "Crossing", + "dri": "Dribbling", + "fin": "Finishing", + "fir": "First Touch", + "fre": "Free Kick", + "hea": "Heading", + "lon": "Long Shots", + "l th": "Long Throws", + "mar": "Marking", + "pas": "Passing", + "pen": "Penalty Taking", + "tck": "Tackling", + "tec": "Technique", + "agg": "Aggression", + "ant": "Anticipation", + "bra": "Bravery", + "cmp": "Composure", + "cnt": "Concentration", + "dec": "Decisions", + "det": "Determination", + "fla": "Flair", + "ldr": "Leadership", + "otb": "Off The Ball", + "pos": "Positioning", + "tea": "Teamwork", + "vis": "Vision", + "wor": "Work Rate", + "acc": "Acceleration", + "agi": "Agility", + "bal": "Balance", + "jum": "Jumping Reach", + "nat": "Natural Fitness", + "pac": "Pace", + "sta": "Stamina", + "str": "Strength", + # Goalkeeper + "aer": "Aerial Reach", + "cmd": "Command of Area", + "com": "Communication", + "ecc": "Eccentricity", + "han": "Handling", + "kic": "Kicking", + "1v1": "One on Ones", + "pun": "Punching (Tendency)", + "ref": "Reflexes", + "rus": "Rushing Out (Tendency)", + "thr": "Throwing", + # Alternative spellings + "wk r": "Work Rate", + "work rate": "Work Rate", + "corners": "Corners", + "crossing": "Crossing", + "dribbling": "Dribbling", + "finishing": "Finishing", + "first touch": "First Touch", + "heading": "Heading", + "long shots": "Long Shots", + "long throws": "Long Throws", + "marking": "Marking", + "passing": "Passing", + "tackling": "Tackling", + "technique": "Technique", +} + +# Build reverse lookup: normalized attr name → canonical. +_ALL_ATTRS_LOWER = {a.lower(): a for a in ALL_VISIBLE_ATTRS + GOALKEEPER_ATTRS} + +_TAG_RE = re.compile(r"<[^>]+>") +_WS_RE = re.compile(r"\s+") + + +def _strip_html(text: str) -> str: + """Remove HTML tags and decode entities.""" + text = _TAG_RE.sub("", text) + text = html.unescape(text) + return _WS_RE.sub(" ", text).strip() + + +def _normalize_header(raw: str) -> str | None: + """Map an HTML column header to a canonical attribute name.""" + clean = _strip_html(raw).strip().lower() + # Direct lookup. + if clean in _HEADER_MAP: + return _HEADER_MAP[clean] + if clean in _ALL_ATTRS_LOWER: + return _ALL_ATTRS_LOWER[clean] + # Truncated header: try first 3 chars. + short = clean[:3] + if short in _HEADER_MAP: + return _HEADER_MAP[short] + return None + + +def _extract_tables(html_content: str) -> list[list[list[str]]]: + """Parse HTML tables into a list of row lists. + + Each row is a list of cell strings. Returns list of tables. + """ + tables: list[list[list[str]]] = [] + table_re = re.compile( + r"]*>(.*?)
", + re.DOTALL | re.IGNORECASE, + ) + row_re = re.compile( + r"]*>(.*?)", + re.DOTALL | re.IGNORECASE, + ) + cell_re = re.compile( + r"]*>(.*?)", + re.DOTALL | re.IGNORECASE, + ) + + for table_match in table_re.finditer(html_content): + rows: list[list[str]] = [] + for row_match in row_re.finditer(table_match.group(1)): + cells = [ + _strip_html(c.group(1)) for c in cell_re.finditer(row_match.group(1)) + ] + if cells: + rows.append(cells) + if rows: + tables.append(rows) + return tables + + +_MIN_TABLE_ROWS = 2 +_MIN_ATTR_VAL = 1 +_MAX_ATTR_VAL = 20 + +# Map from lowercase header text to _ColMap field name. +_HDR_FIELD: dict[str, str] = { + "name": "name", + "player": "name", + "club": "club", + "team": "club", + "nat": "nat", + "nationality": "nat", + "position": "pos", + "pos": "pos", + "ca": "ca", + "ability": "ca", + "pa": "pa", + "potential": "pa", + "value": "value", + "val": "value", + "wage": "wage", +} + +# Map from _ColMap field name to Player attribute name. +_FIELD_ATTR: list[tuple[str, str]] = [ + ("club", "club"), + ("nat", "nationality"), + ("pos", "position"), + ("value", "value"), + ("wage", "wage"), +] + + +@dataclass +class _ColMap: + """Column index mapping from parsed HTML table headers.""" + + name: int | None = None + club: int | None = None + nat: int | None = None + pos: int | None = None + ca: int | None = None + pa: int | None = None + value: int | None = None + wage: int | None = None + attrs: dict[int, str] = field(default_factory=dict) + + +def _build_col_map(headers: list[str]) -> _ColMap: + """Build column index mapping from table header cells.""" + cols = _ColMap() + for i, hdr in enumerate(headers): + h = hdr.strip().lower() + if field := _HDR_FIELD.get(h): + setattr(cols, field, i) + elif attr_name := _normalize_header(hdr): + cols.attrs[i] = attr_name + return cols + + +def _apply_attr(player: Player, attr_name: str, val_str: str) -> None: + """Parse val_str and set an attribute on player if value is in range.""" + if "-" in val_str and val_str[0].isdigit(): + val_str = val_str.split("-", maxsplit=1)[0] + with contextlib.suppress(ValueError): + val = int(val_str) + if _MIN_ATTR_VAL <= val <= _MAX_ATTR_VAL: + if attr_name in ALL_VISIBLE_ATTRS: + player.attributes[attr_name] = val + else: + player.gk_attributes[attr_name] = val + + +def _parse_player_row(row: list[str], cols: _ColMap) -> Player | None: + """Parse one data row into a Player; returns None if row is invalid.""" + if cols.name is None or len(row) <= cols.name: + return None + name = row[cols.name].strip() + if not name: + return None + player = Player(name=name, source="html") + + def _get(col: int | None) -> str | None: + return row[col].strip() if col is not None and col < len(row) else None + + for col_field, attr in _FIELD_ATTR: + if val := _get(getattr(cols, col_field)): + setattr(player, attr, val) + with contextlib.suppress(ValueError, TypeError): + if raw := _get(cols.ca): + player.current_ability = int(raw) + with contextlib.suppress(ValueError, TypeError): + if raw := _get(cols.pa): + player.potential_ability = int(raw) + for col_idx, attr_name in cols.attrs.items(): + if col_idx < len(row): + _apply_attr(player, attr_name, row[col_idx].strip()) + return player + + +def parse_html_export(filepath: Path) -> list[Player]: + """Parse an FM24 HTML export file into Player objects. + + Looks for tables where column headers map to known FM + attributes. The 'Name' column is required. + """ + content = filepath.read_text(encoding="utf-8", errors="replace") + all_players: list[Player] = [] + for table in _extract_tables(content): + if len(table) < _MIN_TABLE_ROWS: + continue + cols = _build_col_map(table[0]) + if cols.name is None: + continue + for row in table[1:]: + player = _parse_player_row(row, cols) + if player is not None: + all_players.append(player) + return all_players + + +def merge_players( + binary_players: list[Player], + html_players: list[Player], +) -> list[Player]: + """Merge binary-parsed data with HTML-imported attributes. + + Matches by name (case-insensitive). Binary provides + DOB/CA/personality; HTML provides visible attributes. + """ + html_by_name: dict[str, Player] = {} + for p in html_players: + html_by_name[p.name.lower()] = p + + merged: list[Player] = [] + matched_names: set[str] = set() + + for bp in binary_players: + key = bp.name.lower() + if key in html_by_name: + hp = html_by_name[key] + bp.attributes = hp.attributes + bp.gk_attributes = hp.gk_attributes + bp.club = hp.club or bp.club + bp.nationality = hp.nationality or bp.nationality + bp.position = hp.position or bp.position + bp.value = hp.value or bp.value + bp.wage = hp.wage or bp.wage + if hp.current_ability > 0: + bp.current_ability = hp.current_ability + if hp.potential_ability > 0: + bp.potential_ability = hp.potential_ability + bp.source = "merged" + matched_names.add(key) + merged.append(bp) + + # Add HTML-only players not matched. + merged.extend(hp for hp in html_players if hp.name.lower() not in matched_names) + + return merged diff --git a/python_pkg/fm24_searcher/models.py b/python_pkg/fm24_searcher/models.py new file mode 100644 index 0000000..89e56b8 --- /dev/null +++ b/python_pkg/fm24_searcher/models.py @@ -0,0 +1,147 @@ +"""Data model for FM24 players.""" + +from __future__ import annotations + +from dataclasses import dataclass, field + +# FM24 visible attribute names grouped by category. +TECHNICAL_ATTRS: list[str] = [ + "Corners", + "Crossing", + "Dribbling", + "Finishing", + "First Touch", + "Free Kick", + "Heading", + "Long Shots", + "Long Throws", + "Marking", + "Passing", + "Penalty Taking", + "Tackling", + "Technique", +] + +MENTAL_ATTRS: list[str] = [ + "Aggression", + "Anticipation", + "Bravery", + "Composure", + "Concentration", + "Decisions", + "Determination", + "Flair", + "Leadership", + "Off The Ball", + "Positioning", + "Teamwork", + "Vision", + "Work Rate", +] + +PHYSICAL_ATTRS: list[str] = [ + "Acceleration", + "Agility", + "Balance", + "Jumping Reach", + "Natural Fitness", + "Pace", + "Stamina", + "Strength", +] + +GOALKEEPER_ATTRS: list[str] = [ + "Aerial Reach", + "Command of Area", + "Communication", + "Eccentricity", + "First Touch (GK)", + "Handling", + "Kicking", + "One on Ones", + "Passing (GK)", + "Punching (Tendency)", + "Reflexes", + "Rushing Out (Tendency)", + "Throwing", +] + +ALL_VISIBLE_ATTRS: list[str] = TECHNICAL_ATTRS + MENTAL_ATTRS + PHYSICAL_ATTRS + + +@dataclass +class Player: + """A single FM24 player record.""" + + # Identity (from binary or HTML). + uid: int = 0 + name: str = "" + + # Biographical. + date_of_birth: str = "" # ISO format YYYY-MM-DD + nationality: str = "" + club: str = "" + position: str = "" + + # Ability ratings (from binary — may be approximate). + current_ability: int = 0 + potential_ability: int = 0 + + # Hidden personality bytes (from binary). + personality: list[int] = field(default_factory=list) + + # Visible attributes (1-20 scale). Key = attribute name. + attributes: dict[str, int] = field(default_factory=dict) + + # Goalkeeper attributes. + gk_attributes: dict[str, int] = field(default_factory=dict) + + # Monetary values. + value: str = "" + wage: str = "" + + # Data source for traceability. + source: str = "" # "binary", "html", or "merged" + + def get_attr(self, name: str) -> int: + """Get an attribute value by name, 0 if missing.""" + return self.attributes.get(name, 0) + + def weighted_score( + self, + weights: dict[str, float], + ) -> float: + """Compute weighted attribute score for scouting.""" + total = 0.0 + weight_sum = 0.0 + for attr_name, weight in weights.items(): + val = self.get_attr(attr_name) + if val > 0: + total += val * weight + weight_sum += weight + if weight_sum == 0: + return 0.0 + return total / weight_sum + + def matches_filter( + self, + min_attrs: dict[str, int] | None = None, + min_ca: int | None = None, + position_filter: str | None = None, + nationality_filter: str | None = None, + club_filter: str | None = None, + ) -> bool: + """Check if this player matches all given filters.""" + if min_attrs: + for attr_name, min_val in min_attrs.items(): + if self.get_attr(attr_name) < min_val: + return False + if min_ca and self.current_ability < min_ca: + return False + if position_filter and position_filter.lower() not in (self.position.lower()): + return False + if nationality_filter and nationality_filter.lower() not in ( + self.nationality.lower() + ): + return False + return not (club_filter and club_filter.lower() not in self.club.lower()) diff --git a/python_pkg/fm24_searcher/tests/__init__.py b/python_pkg/fm24_searcher/tests/__init__.py new file mode 100644 index 0000000..7cadd8e --- /dev/null +++ b/python_pkg/fm24_searcher/tests/__init__.py @@ -0,0 +1 @@ +"""Tests for FM24 Database Searcher.""" diff --git a/python_pkg/fm24_searcher/tests/conftest.py b/python_pkg/fm24_searcher/tests/conftest.py new file mode 100644 index 0000000..875d212 --- /dev/null +++ b/python_pkg/fm24_searcher/tests/conftest.py @@ -0,0 +1,13 @@ +"""Shared fixtures for FM24 searcher tests.""" + +from __future__ import annotations + +import os + +import pytest + + +@pytest.fixture(autouse=True, scope="session") +def _offscreen_qt() -> None: + """Force offscreen Qt platform for headless testing.""" + os.environ["QT_QPA_PLATFORM"] = "offscreen" diff --git a/python_pkg/fm24_searcher/tests/test_binary_parser.py b/python_pkg/fm24_searcher/tests/test_binary_parser.py new file mode 100644 index 0000000..e96c301 --- /dev/null +++ b/python_pkg/fm24_searcher/tests/test_binary_parser.py @@ -0,0 +1,721 @@ +"""Tests for python_pkg.fm24_searcher.binary_parser.""" + +from __future__ import annotations + +import struct +from typing import TYPE_CHECKING +from unittest.mock import MagicMock, patch + +import pytest +import zstandard + +from python_pkg.fm24_searcher.binary_parser import ( + ATTR_BLOCK_MAP, + FMF_MAGIC, + REC_SEP, + TAD_MAGIC, + ZSTD_MAGIC, + _attrs_from_block, + _decompress_multiframe, + _decompress_single, + _dob_from_bytes, + _enrich_with_attributes, + _find_all_attr_blocks, + _find_name_boundaries, + _is_valid_attr_block, + _is_valid_name, + _pass1_separator_walk, + _pass2_regex_scan, + _try_extract_player, + decompress_file, + parse_people_db, + search_players, +) +from python_pkg.fm24_searcher.models import Player + +if TYPE_CHECKING: + from pathlib import Path + + +def _zstd_compress(data: bytes) -> bytes: + """Compress data with zstd for test fixtures.""" + cctx = zstandard.ZstdCompressor() + result: bytes = cctx.compress(data) + return result + + +def _make_tad(payload: bytes) -> bytes: + """Create a TAD-formatted blob.""" + return TAD_MAGIC + _zstd_compress(payload) + + +def _make_player_record( + name: str, + *, + day_of_year: int = 180, + year: int = 1995, + personality: bytes = b"\x0a\x0b\x0c\x0d\x0e\x0f\x10\x05", + prefix: int = 0x00, +) -> bytes: + """Build a minimal binary player record. + + Layout: 0x00 + uint32 name_len + name_utf8 + DOB(4) + + 5 flag bytes + 2 bytes "nat_id" + 2 zeros + 4 bytes "ref" + + 8 personality bytes. + """ + name_bytes = name.encode("utf-8") + rec = bytes([prefix]) + rec += struct.pack(" None: + payload = b"hello world" + raw = _make_tad(payload) + assert _decompress_single(raw) == payload + + def test_bad_magic(self) -> None: + with pytest.raises(ValueError, match="Expected TAD magic"): + _decompress_single(b"\x00" * 8 + b"data") + + +class TestDecompressMultiframe: + """_decompress_multiframe tests.""" + + def test_multiple_frames(self) -> None: + frame1 = _zstd_compress(b"frame1") + frame2 = _zstd_compress(b"frame2") + raw = b"header" + frame1 + b"gap" + frame2 + result = _decompress_multiframe(raw) + assert b"frame1" in result + assert b"frame2" in result + + def test_no_frames(self) -> None: + result = _decompress_multiframe(b"no zstd here") + assert result == [] + + def test_corrupt_frame_skipped(self) -> None: + # Put the magic bytes but no valid zstd data after. + raw = ZSTD_MAGIC + b"\x00\x00\x00" + result = _decompress_multiframe(raw) + assert result == [] + + +class TestDecompressFile: + """decompress_file tests.""" + + def test_tad_file(self, tmp_path: Path) -> None: + p = tmp_path / "test.dat" + p.write_bytes(_make_tad(b"payload")) + assert decompress_file(p) == b"payload" + + def test_fmf_file(self, tmp_path: Path) -> None: + frame = _zstd_compress(b"content") + raw = FMF_MAGIC + b"\x00" * 10 + frame + p = tmp_path / "test.dat" + p.write_bytes(raw) + result = decompress_file(p) + assert isinstance(result, list) + assert b"content" in result + + def test_unknown_format(self, tmp_path: Path) -> None: + p = tmp_path / "test.dat" + p.write_bytes(b"\xff" * 20) + with pytest.raises(ValueError, match="Unknown file format"): + decompress_file(p) + + +class TestDobFromBytes: + """_dob_from_bytes tests.""" + + def test_valid_dob(self) -> None: + data = struct.pack(" None: + data = struct.pack(" None: + data = struct.pack(" None: + data = struct.pack(" None: + data = struct.pack(" None: + data = struct.pack(" None: + data = struct.pack(" None: + data = struct.pack(" None: + # Day 366 in a non-leap year overflows to next year. + data = struct.pack(" None: + """Cover except (ValueError, OverflowError) branch (lines 115-116).""" + data = struct.pack(" None: + prefix = b"\xff\xff" + data = prefix + struct.pack(" None: + name = "John Smith" + name_bytes = name.encode("utf-8") + data = b"\x00" + struct.pack(" None: + data = b"\xff" * 100 + assert _find_name_boundaries(data, 50) is None + + def test_invalid_utf8(self) -> None: + raw_name = b"\x80\x81\x82\x83" + data = b"\x00" + struct.pack(" None: + name = b"AB" # 2 bytes, below _BOUNDARY_MIN_NAME_LEN (3) + data = b"\x00" + struct.pack(" None: + # name_pos at 0 means off = 0 - back - 4 < 0. + assert _find_name_boundaries(b"\x00" * 10, 0) is None + + def test_non_printable_name(self) -> None: + name = "Test\x01Name" + name_bytes = name.encode("utf-8") + data = b"\x00" + struct.pack(" None: + """Valid name_len found but name_pos not in [ns, ne) (137→128).""" + # Place valid length=5 at offset 10. + data = b"\xff" * 10 + struct.pack(" None: + data = b"John Smith" + assert _is_valid_name(data, 0, 10) == "John Smith" + + def test_beyond_data(self) -> None: + data = b"Short" + assert _is_valid_name(data, 0, 100) == "" + + def test_invalid_utf8(self) -> None: + data = b"\x80\x81\x82" + assert _is_valid_name(data, 0, 3) == "" + + def test_no_alpha(self) -> None: + data = b"123 456" + assert _is_valid_name(data, 0, 7) == "" + + def test_non_printable(self) -> None: + data = b"Test\x00Name" + assert _is_valid_name(data, 0, len(data)) == "" + + def test_with_offset(self) -> None: + data = b"\xff\xffJohn" + assert _is_valid_name(data, 2, 4) == "John" + + def test_trailing_non_alpha_rejected(self) -> None: + """Names ending with punctuation like '<' are rejected.""" + data = b"John Smith<" + assert _is_valid_name(data, 0, len(data)) == "" + + +class TestTryExtractPlayer: + """_try_extract_player tests.""" + + def test_valid_record(self) -> None: + rec = _make_player_record("John Smith", day_of_year=180, year=1995) + padding = b"\x00" * 10 # trailing space + result = _try_extract_player(rec + padding, 0) + assert result is not None + player, _ne = result + assert player.name == "John Smith" + assert player.date_of_birth == "1995-06-29" + assert player.uid == 0 # prefix_offset + assert player.source == "binary" + assert len(player.personality) == 8 + + def test_too_short(self) -> None: + assert _try_extract_player(b"\x00" * 10, 0) is None + + def test_bad_prefix(self) -> None: + data = b"\x01" + b"\x00" * 50 + assert _try_extract_player(data, 0) is None + + def test_name_too_short(self) -> None: + # Name len = 1 (below _EXTRACT_MIN_NAME_LEN = 3). + data = b"\x00" + struct.pack(" None: + # Name len = 2 (below _EXTRACT_MIN_NAME_LEN = 3). + data = b"\x00" + struct.pack(" None: + data = b"\x00" + struct.pack(" None: + # Name with only digits. + data = b"\x00" + struct.pack(" None: + name = b"John" + # Need len >= prefix_offset+30=30 to pass first check, + # but ne+25 > len to hit line 196. ne = 5+4 = 9, need < 34. + # 1 prefix + 4 uint32 + 4 name + 21 padding = 30 bytes total. + data = b"\x00" + struct.pack(" 30 + assert _try_extract_player(data, 0) is None + + def test_invalid_personality(self) -> None: + rec = _make_player_record( + "Test Player", + personality=b"\xff\xff\xff\xff\xff\xff\xff\xff", + ) + padding = b"\x00" * 10 + result = _try_extract_player(rec + padding, 0) + assert result is not None + player, _ = result + assert player.personality == [] + + def test_nonzero_prefix_offset(self) -> None: + prefix = b"\xff" * 10 + rec = _make_player_record("Jane Doe") + padding = b"\x00" * 10 + result = _try_extract_player(prefix + rec + padding, 10) + assert result is not None + assert result[0].uid == 10 + assert result[0].name == "Jane Doe" + + +class TestPass1SeparatorWalk: + """_pass1_separator_walk tests.""" + + def test_finds_records(self) -> None: + rec = _make_player_record("John Smith") + # Build data: 12-byte header + separator + record + padding. + data = b"\x00" * 12 + REC_SEP + rec + b"\x00" * 30 + players: list[Player] = [] + seen: set[int] = set() + _pass1_separator_walk(data, players, seen) + assert len(players) >= 1 + assert players[0].name == "John Smith" + + def test_dedup_by_offset(self) -> None: + rec = _make_player_record("John Smith") + data = b"\x00" * 12 + REC_SEP + rec + b"\x00" * 30 + players: list[Player] = [] + expected_offset = 12 + len(REC_SEP) + seen: set[int] = {expected_offset} + _pass1_separator_walk(data, players, seen) + assert len(players) == 0 + + def test_invalid_record_advances(self) -> None: + # Separator followed by non-zero prefix byte. + data = b"\x00" * 12 + REC_SEP + b"\x01" + b"\x00" * 50 + players: list[Player] = [] + seen: set[int] = set() + _pass1_separator_walk(data, players, seen) + assert len(players) == 0 + + def test_no_separators(self) -> None: + data = b"\x00" * 100 + players: list[Player] = [] + seen: set[int] = set() + _pass1_separator_walk(data, players, seen) + assert len(players) == 0 + + +class TestPass2RegexScan: + """_pass2_regex_scan tests.""" + + def test_finds_records(self) -> None: + rec = _make_player_record("Anna Baker") + data = b"\x00" * 50 + rec + b"\x00" * 30 + players: list[Player] = [] + seen: set[int] = set() + _pass2_regex_scan(data, players, seen) + found = [p for p in players if p.name == "Anna Baker"] + assert len(found) >= 1 + + def test_dedup_seen(self) -> None: + rec = _make_player_record("Anna Baker") + offset = 50 + data = b"\x00" * offset + rec + b"\x00" * 30 + players: list[Player] = [] + seen: set[int] = {offset} + _pass2_regex_scan(data, players, seen) + found = [p for p in players if p.name == "Anna Baker"] + assert len(found) == 0 + + def test_progress_callback(self) -> None: + rec = _make_player_record("Test Player") + data = b"\x00" * 50 + rec + b"\x00" * 30 + cb = MagicMock() + players: list[Player] = [] + seen: set[int] = set() + _pass2_regex_scan(data, players, seen, progress_cb=cb) + # Callback should be called at i=0 (0 % 50000 == 0). + if players: + cb.assert_called() + + def test_no_dob_or_multiword_skipped(self) -> None: + # Single-word name without DOB → should be skipped. + name = b"Noname" + rec = b"\x00" + struct.pack(" None: + """Regex matches but _try_extract_player returns None (254→261).""" + # Regex pattern: \x00, len_byte, \x00\x00\x00, [A-Z]. + # Name starts with 'A' (matches regex) but rest is non-printable. + rec = b"\x00\x05\x00\x00\x00A\x01\x01\x01\x01" + b"\x00" * 30 + data = b"\xff" * 50 + rec + b"\x00" * 30 + players: list[Player] = [] + seen: set[int] = set() + _pass2_regex_scan(data, players, seen) + assert len(players) == 0 + + +class TestParsePeopleDb: + """parse_people_db integration tests with synthetic data.""" + + def _make_db(self, tmp_path: Path, player_names: list[str]) -> Path: + """Build a minimal TAD database file.""" + # 8 byte inner header + uint32 record_count. + inner = b"\x00" * 8 + struct.pack(" None: + filepath = self._make_db(tmp_path, ["Alpha Beta", "Gamma Delta"]) + cb = MagicMock() + players = parse_people_db(filepath, progress_cb=cb) + assert len(players) >= 2 + names = {p.name for p in players} + assert "Alpha Beta" in names + assert "Gamma Delta" in names + cb.assert_called() + + def test_parse_no_progress(self, tmp_path: Path) -> None: + filepath = self._make_db(tmp_path, ["Just Name"]) + players = parse_people_db(filepath) + assert any(p.name == "Just Name" for p in players) + + def test_empty_db(self, tmp_path: Path) -> None: + filepath = self._make_db(tmp_path, []) + players = parse_people_db(filepath) + assert isinstance(players, list) + + +def _make_attr_block( + *, + vals: dict[int, int] | None = None, +) -> list[int]: + """Build a valid 63-byte attribute block. + + Zeros at positions 20-25 and 40-42, reasonable + non-zero values at all confirmed attribute positions. + Overrides via *vals*. + """ + block = [0] * 63 + # Fill confirmed positions with default non-zero values. + defaults: dict[int, int] = { + 9: 15, + 10: 20, + 11: 17, + 12: 10, + 13: 16, + 14: 4, + 15: 14, + 16: 19, + 17: 17, + 18: 7, + 19: 20, + 26: 16, + 27: 18, + 29: 5, + 31: 19, + 32: 20, + 33: 20, + 34: 12, + 35: 20, + 36: 15, + 37: 14, + 38: 9, + 39: 4, + 43: 16, + 44: 18, + 45: 9, + 46: 13, + 47: 15, + 48: 6, + 49: 14, + 50: 9, + 51: 18, + 52: 10, + 53: 16, + 54: 7, + 55: 15, + 56: 18, + 57: 6, + 58: 12, + 59: 14, + 60: 20, + 61: 16, + 62: 13, + } + for pos, val in defaults.items(): + block[pos] = val + if vals: + for pos, val in vals.items(): + block[pos] = val + return block + + +class TestIsValidAttrBlock: + """_is_valid_attr_block tests.""" + + def test_valid_block(self) -> None: + block = bytes(_make_attr_block()) + assert _is_valid_attr_block(block) is True + + def test_value_above_20_rejected(self) -> None: + raw = _make_attr_block() + raw[9] = 21 + assert _is_valid_attr_block(bytes(raw)) is False + + def test_nonzero_in_zero_range_rejected(self) -> None: + raw = _make_attr_block() + raw[22] = 1 + assert _is_valid_attr_block(bytes(raw)) is False + + def test_nonzero_at_pos_40_rejected(self) -> None: + raw = _make_attr_block() + raw[40] = 1 + assert _is_valid_attr_block(bytes(raw)) is False + + def test_too_few_nonzero_rejected(self) -> None: + block = bytes([0] * 63) + assert _is_valid_attr_block(block) is False + + def test_exactly_min_nonzero(self) -> None: + raw = [0] * 63 + # Fill exactly 30 positions outside zero ranges. + keep = [ + i for i in range(63) if i not in range(20, 26) and i not in {40, 41, 42} + ][:30] + for pos in keep: + raw[pos] = 10 + assert _is_valid_attr_block(bytes(raw)) is True + + +class TestFindAllAttrBlocks: + """_find_all_attr_blocks tests.""" + + def test_single_block_found(self) -> None: + block = bytes(_make_attr_block()) + data = b"\xff" * 100 + block + b"\xff" * 100 + offsets, values = _find_all_attr_blocks(data) + assert len(offsets) == 1 + assert offsets[0] == 100 + assert values[0] == list(block) + + def test_multiple_blocks(self) -> None: + blk1 = bytes(_make_attr_block(vals={9: 15})) + blk2 = bytes(_make_attr_block(vals={9: 18})) + data = b"\xff" * 50 + blk1 + b"\xff" * 200 + blk2 + b"\xff" * 50 + offsets, _values = _find_all_attr_blocks(data) + assert len(offsets) == 2 + assert offsets[0] < offsets[1] + + def test_no_blocks_in_random_data(self) -> None: + data = bytes(range(256)) * 10 + offsets, _values = _find_all_attr_blocks(data) + assert offsets == [] + + def test_empty_data(self) -> None: + offsets, values = _find_all_attr_blocks(b"") + assert offsets == [] + assert values == [] + + def test_block_at_start(self) -> None: + block = bytes(_make_attr_block()) + data = block + b"\xff" * 100 + offsets, _ = _find_all_attr_blocks(data) + assert len(offsets) == 1 + assert offsets[0] == 0 + + def test_data_too_short_for_block(self) -> None: + data = b"\x00" * 30 + offsets, _ = _find_all_attr_blocks(data) + assert offsets == [] + + +class TestAttrsFromBlock: + """_attrs_from_block tests.""" + + def test_extracts_confirmed_attrs(self) -> None: + block = _make_attr_block() + attrs = _attrs_from_block(block) + assert attrs["Crossing"] == 15 + assert attrs["Technique"] == 20 + assert attrs["Determination"] == 20 + assert attrs["Concentration"] == 13 + assert attrs["Strength"] == 9 + assert attrs["Jumping Reach"] == 6 + assert attrs["Agility"] == 18 + assert attrs["Stamina"] == 12 + assert attrs["Pace"] == 16 + assert len(attrs) == len(ATTR_BLOCK_MAP) + + def test_zero_values_excluded(self) -> None: + block = _make_attr_block(vals={9: 0}) + attrs = _attrs_from_block(block) + assert "Crossing" not in attrs + + def test_all_mapped_positions_zero(self) -> None: + block = [0] * 63 + attrs = _attrs_from_block(block) + assert attrs == {} + + +class TestEnrichWithAttributes: + """_enrich_with_attributes tests.""" + + def test_block_assigned_to_nearby_player(self) -> None: + block = bytes(_make_attr_block()) + data = b"\xff" * 100 + block + b"\xff" * 100 + player = Player(uid=200, name="Test") + _enrich_with_attributes(data, [player]) + assert player.attributes.get("Crossing") == 15 + + def test_block_too_far_away_ignored(self) -> None: + block = bytes(_make_attr_block()) + data = b"\xff" * 10 + block + b"\xff" * 2000 + player = Player(uid=2000, name="Test") + _enrich_with_attributes(data, [player]) + assert player.attributes == {} + + def test_no_blocks_found(self) -> None: + data = bytes(range(256)) * 10 + player = Player(uid=100, name="Test") + _enrich_with_attributes(data, [player]) + assert player.attributes == {} + + def test_player_before_all_blocks(self) -> None: + block = bytes(_make_attr_block()) + data = b"\xff" * 500 + block + b"\xff" * 100 + player = Player(uid=10, name="Test") + _enrich_with_attributes(data, [player]) + assert player.attributes == {} + + def test_progress_callback(self) -> None: + block = bytes(_make_attr_block()) + data = b"\xff" * 100 + block + b"\xff" * 100 + player = Player(uid=200, name="Test") + cb = MagicMock() + _enrich_with_attributes(data, [player], progress_cb=cb) + assert cb.call_count >= 2 + + def test_multiple_players_multiple_blocks(self) -> None: + blk1 = bytes(_make_attr_block(vals={9: 12})) + blk2 = bytes(_make_attr_block(vals={9: 18})) + gap = b"\xff" * 200 + data = gap + blk1 + gap + gap + blk2 + gap + off1 = 200 + off2 = 200 + 63 + 200 + 200 + p1 = Player(uid=off1 + 63 + 50, name="Player1") + p2 = Player(uid=off2 + 63 + 50, name="Player2") + _enrich_with_attributes(data, [p1, p2]) + assert p1.attributes.get("Crossing") == 12 + assert p2.attributes.get("Crossing") == 18 + + +class TestSearchPlayers: + """search_players tests.""" + + def test_match(self) -> None: + players = [Player(name="John Smith"), Player(name="Jane Doe")] + result = search_players(players, "john") + assert len(result) == 1 + assert result[0].name == "John Smith" + + def test_no_match(self) -> None: + players = [Player(name="John Smith")] + assert search_players(players, "xyz") == [] + + def test_case_insensitive(self) -> None: + players = [Player(name="John Smith")] + assert len(search_players(players, "JOHN")) == 1 + + def test_partial_match(self) -> None: + players = [Player(name="Jonathan")] + assert len(search_players(players, "jona")) == 1 diff --git a/python_pkg/fm24_searcher/tests/test_cli.py b/python_pkg/fm24_searcher/tests/test_cli.py new file mode 100644 index 0000000..ddfe7ea --- /dev/null +++ b/python_pkg/fm24_searcher/tests/test_cli.py @@ -0,0 +1,373 @@ +"""Tests for python_pkg.fm24_searcher.cli.""" + +from __future__ import annotations + +import struct +from typing import TYPE_CHECKING + +import zstandard + +from python_pkg.fm24_searcher.binary_parser import TAD_MAGIC +from python_pkg.fm24_searcher.cli import ( + _DEFAULT_LIMIT, + _format_player, + _format_tsv_header, + _format_tsv_row, + _print_stats, + build_parser, + run_dump, +) +from python_pkg.fm24_searcher.models import ALL_VISIBLE_ATTRS, Player + +if TYPE_CHECKING: + from pathlib import Path + + import pytest + + +def _zstd_compress(data: bytes) -> bytes: + """Compress data with zstd.""" + cctx = zstandard.ZstdCompressor() + result: bytes = cctx.compress(data) + return result + + +def _make_tad(payload: bytes) -> bytes: + """Create a TAD-formatted blob.""" + return TAD_MAGIC + _zstd_compress(payload) + + +def _make_player_record( + name: str, + *, + day_of_year: int = 180, + year: int = 1995, +) -> bytes: + """Build a minimal binary player record.""" + name_bytes = name.encode("utf-8") + rec = b"\x00" + rec += struct.pack(" Path: + """Build a minimal TAD database file.""" + sep = b"\x05\x00\x00\x00\x00" + inner = b"\x00" * 8 + struct.pack(" None: + p = Player( + name="Test Player", + date_of_birth="1995-06-29", + source="binary", + uid=100, + ) + result = _format_player(p) + assert "=== Test Player ===" in result + assert "DOB: 1995-06-29" in result + assert "Source: binary" in result + assert "UID (byte offset): 100" in result + + def test_with_attrs(self) -> None: + p = Player( + name="Star", + attributes={"Crossing": 15, "Finishing": 18}, + source="binary", + ) + result = _format_player(p, show_attrs=True) + assert "Crossing: 15" in result + assert "Finishing: 18" in result + assert "Missing attrs:" in result + + def test_no_attrs_block(self) -> None: + p = Player(name="Noattr", source="binary") + result = _format_player(p, show_attrs=True) + assert "(no attribute block found)" in result + + def test_all_optional_fields(self) -> None: + p = Player( + name="Full", + current_ability=160, + potential_ability=190, + nationality="Argentina", + club="Inter Miami", + position="AM (R,C), ST", + personality=[10, 11, 12, 13, 14, 15, 16, 5], + source="binary", + ) + result = _format_player(p) + assert "CA: 160" in result + assert "PA: 190" in result + assert "Nationality: Argentina" in result + assert "Club: Inter Miami" in result + assert "Position: AM (R,C), ST" in result + assert "Personality bytes:" in result + + def test_no_optional_fields(self) -> None: + p = Player(name="Minimal", source="binary", uid=0) + result = _format_player(p) + assert "DOB:" not in result + assert "CA:" not in result + assert "PA:" not in result + + def test_attrs_all_present(self) -> None: + attrs = dict.fromkeys(ALL_VISIBLE_ATTRS, 10) + p = Player(name="Complete", attributes=attrs, source="binary") + result = _format_player(p, show_attrs=True) + assert "Missing attrs:" not in result + + def test_attrs_show_false(self) -> None: + p = Player( + name="Skip", + attributes={"Crossing": 15}, + source="binary", + ) + result = _format_player(p, show_attrs=False) + assert "Crossing" not in result + + +class TestFormatTsv: + """TSV formatting tests.""" + + def test_header_without_attrs(self) -> None: + hdr = _format_tsv_header(show_attrs=False) + assert hdr == "Name\tDOB\tCA\tPA\tPersonality\tUID" + + def test_header_with_attrs(self) -> None: + hdr = _format_tsv_header(show_attrs=True) + assert "Corners" in hdr + assert "Acceleration" in hdr + + def test_row_basic(self) -> None: + p = Player( + name="John", + date_of_birth="1995-01-01", + personality=[1, 2, 3], + uid=50, + source="binary", + ) + row = _format_tsv_row(p, show_attrs=False) + parts = row.split("\t") + assert parts[0] == "John" + assert parts[1] == "1995-01-01" + assert parts[4] == "1,2,3" + assert parts[5] == "50" + + def test_row_with_attrs(self) -> None: + p = Player( + name="Star", + attributes={"Crossing": 15}, + uid=10, + source="binary", + ) + row = _format_tsv_row(p, show_attrs=True) + assert "15" in row + + def test_row_empty_fields(self) -> None: + p = Player(name="Empty", uid=0, source="binary") + row = _format_tsv_row(p, show_attrs=False) + parts = row.split("\t") + assert parts[2] == "" # CA + assert parts[3] == "" # PA + + +class TestBuildParser: + """build_parser tests.""" + + def test_defaults(self) -> None: + parser = build_parser() + args = parser.parse_args(["--dump"]) + assert args.dump is True + assert args.search == "" + assert args.limit == _DEFAULT_LIMIT + assert args.attrs is False + assert args.tsv is False + + def test_all_flags(self) -> None: + parser = build_parser() + args = parser.parse_args( + [ + "--dump", + "--search", + "Messi", + "--limit", + "10", + "--attrs", + "--tsv", + "--with-attrs-only", + "--stats", + ] + ) + assert args.search == "Messi" + assert args.limit == 10 + assert args.attrs is True + assert args.tsv is True + assert args.with_attrs_only is True + assert args.stats is True + + def test_custom_db(self, tmp_path: Path) -> None: + parser = build_parser() + db_path = str(tmp_path) + args = parser.parse_args(["--dump", "--db", db_path]) + assert args.db == db_path + + +class TestPrintStats: + """_print_stats tests.""" + + def test_basic_stats(self, capsys: pytest.CaptureFixture[str]) -> None: + players = [ + Player( + name="A", + date_of_birth="1995-01-01", + attributes={"Crossing": 15, "Finishing": 18}, + current_ability=160, + source="binary", + ), + Player(name="B", source="binary"), + ] + _print_stats(players) + out = capsys.readouterr().out + assert "Total players: 2" in out + assert "With DOB: 1" in out + assert "With attributes: 1" in out + assert "With CA/PA: 1" in out + assert "Crossing" in out + + def test_empty_players(self, capsys: pytest.CaptureFixture[str]) -> None: + _print_stats([]) + out = capsys.readouterr().out + assert "Total players: 0" in out + + def test_no_attrs(self, capsys: pytest.CaptureFixture[str]) -> None: + players = [Player(name="X", source="binary")] + _print_stats(players) + out = capsys.readouterr().out + assert "With attributes: 0" in out + # No attr coverage section when no attrs + assert "Attribute coverage" not in out + + +class TestRunDump: + """run_dump integration tests.""" + + def test_no_dump_flag(self) -> None: + assert run_dump([]) == 1 + + def test_db_not_found(self) -> None: + result = run_dump(["--dump", "--db", "/nonexistent/db.dat"]) + assert result == 2 + + def test_dump_text(self, tmp_path: Path) -> None: + db = _make_db(tmp_path, ["Alpha Beta", "Gamma Delta"]) + result = run_dump( + [ + "--dump", + "--db", + str(db), + "--limit", + "5", + ] + ) + assert result == 0 + + def test_dump_text_output( + self, + tmp_path: Path, + capsys: pytest.CaptureFixture[str], + ) -> None: + db = _make_db(tmp_path, ["Alpha Beta"]) + run_dump(["--dump", "--db", str(db), "--limit", "5"]) + out = capsys.readouterr().out + assert "Alpha Beta" in out + assert "Showing" in out + + def test_dump_tsv( + self, + tmp_path: Path, + capsys: pytest.CaptureFixture[str], + ) -> None: + db = _make_db(tmp_path, ["TSV Player"]) + run_dump(["--dump", "--db", str(db), "--tsv"]) + out = capsys.readouterr().out + assert "Name\tDOB\tCA\tPA" in out + assert "TSV Player" in out + + def test_dump_with_search( + self, + tmp_path: Path, + capsys: pytest.CaptureFixture[str], + ) -> None: + db = _make_db(tmp_path, ["Alpha Beta", "Gamma Delta"]) + run_dump(["--dump", "--db", str(db), "--search", "alpha"]) + out = capsys.readouterr().out + assert "Alpha Beta" in out + assert "Gamma Delta" not in out + + def test_dump_with_attrs( + self, + tmp_path: Path, + capsys: pytest.CaptureFixture[str], + ) -> None: + db = _make_db(tmp_path, ["Attr Check"]) + run_dump(["--dump", "--db", str(db), "--attrs"]) + out = capsys.readouterr().out + assert "Attr Check" in out + + def test_dump_tsv_with_attrs( + self, + tmp_path: Path, + capsys: pytest.CaptureFixture[str], + ) -> None: + db = _make_db(tmp_path, ["TSV Attrs"]) + run_dump(["--dump", "--db", str(db), "--tsv", "--attrs"]) + out = capsys.readouterr().out + assert "Corners" in out + + def test_dump_with_attrs_only( + self, + tmp_path: Path, + capsys: pytest.CaptureFixture[str], + ) -> None: + db = _make_db(tmp_path, ["No Attrs"]) + run_dump(["--dump", "--db", str(db), "--with-attrs-only"]) + out = capsys.readouterr().out + # No players should have attrs in synthetic data. + assert "Showing 0 of 0" in out + + def test_dump_stats( + self, + tmp_path: Path, + capsys: pytest.CaptureFixture[str], + ) -> None: + db = _make_db(tmp_path, ["Stats Test"]) + result = run_dump(["--dump", "--db", str(db), "--stats"]) + assert result == 0 + out = capsys.readouterr().out + assert "Total players:" in out + + def test_progress_goes_to_stderr( + self, + tmp_path: Path, + capsys: pytest.CaptureFixture[str], + ) -> None: + db = _make_db(tmp_path, ["Progress"]) + run_dump(["--dump", "--db", str(db)]) + err = capsys.readouterr().err + assert "Decompressing" in err diff --git a/python_pkg/fm24_searcher/tests/test_gui.py b/python_pkg/fm24_searcher/tests/test_gui.py new file mode 100644 index 0000000..d95a8dd --- /dev/null +++ b/python_pkg/fm24_searcher/tests/test_gui.py @@ -0,0 +1,1177 @@ +"""Tests for python_pkg.fm24_searcher.gui.""" + +from __future__ import annotations + +import datetime +from pathlib import Path +from unittest.mock import MagicMock, patch + +# Import after conftest sets QT_QPA_PLATFORM=offscreen. +from PyQt6.QtCore import QModelIndex, Qt +from PyQt6.QtGui import QPaintEvent +from PyQt6.QtWidgets import QApplication +import pytest + +from python_pkg.fm24_searcher.gui import ( + _IMPORT_GUIDE, + CompareDialog, + FilterPanel, + LoadingOverlay, + MainWindow, + PlayerTableModel, + WeightPanel, + _attr_color, + _build_tooltip, + _player_age, + main, +) +from python_pkg.fm24_searcher.models import ALL_VISIBLE_ATTRS, Player + + +@pytest.fixture(scope="module") +def qapp() -> QApplication: + """Get or create QApplication for tests.""" + app = QApplication.instance() + if app is None: + app = QApplication([]) + return app + + +def _make_player(**kwargs: object) -> Player: + """Helper to create Player with sensible defaults.""" + defaults: dict[str, object] = { + "name": "Test Player", + "date_of_birth": "1995-06-29", + "current_ability": 170, + "potential_ability": 185, + "source": "binary", + } + defaults.update(kwargs) + return Player(**defaults) + + +class TestPlayerAge: + """_player_age tests.""" + + def test_valid_dob(self) -> None: + p = Player(date_of_birth="1995-06-29") + age = _player_age(p) + today = datetime.datetime.now(tz=datetime.UTC).date() + expected = today.year - 1995 + if (today.month, today.day) < (6, 29): + expected -= 1 + assert age == expected + + def test_no_dob(self) -> None: + assert _player_age(Player()) == 0 + + def test_invalid_dob(self) -> None: + assert _player_age(Player(date_of_birth="not-a-date")) == 0 + + def test_birthday_edge_before(self) -> None: + # Birthday is Dec 31 — hasn't happened yet if today < Dec 31. + today = datetime.datetime.now(tz=datetime.UTC).date() + p = Player(date_of_birth=f"{today.year - 20}-12-31") + age = _player_age(p) + if (today.month, today.day) < (12, 31): + assert age == 19 + else: + assert age == 20 + + def test_birthday_edge_after(self) -> None: + p = Player(date_of_birth="2000-01-01") + age = _player_age(p) + today = datetime.datetime.now(tz=datetime.UTC).date() + expected = today.year - 2000 + if (today.month, today.day) < (1, 1): + expected -= 1 # Can't happen: Jan 1 is always ≤ today + assert age == expected + + +class TestAttrColor: + """_attr_color thresholds.""" + + def test_excellent(self) -> None: + c = _attr_color(20) + assert c.green() == 150 + + def test_good(self) -> None: + c = _attr_color(16) + assert c.green() == 180 + + def test_average(self) -> None: + c = _attr_color(13) + assert c.green() == 180 + assert c.red() == 180 + + def test_below(self) -> None: + c = _attr_color(9) + assert c.red() == 220 + + def test_poor(self) -> None: + c = _attr_color(3) + assert c.red() == 200 + + +class TestBuildTooltip: + """_build_tooltip tests.""" + + def test_full(self) -> None: + p = Player( + name="John", + club="Madrid", + nationality="Spain", + position="AMC", + date_of_birth="1995-06-29", + value="€50M", + wage="€200K", + personality=[10, 11, 12, 13, 14, 15, 16, 5], + ) + tip = _build_tooltip(p) + assert "John" in tip + assert "Club: Madrid" in tip + assert "Nationality: Spain" in tip + assert "Position: AMC" in tip + assert "DOB: 1995-06-29" in tip + assert "Value: €50M" in tip + assert "Wage: €200K" in tip + assert "Personality:" in tip + + def test_minimal(self) -> None: + p = Player(name="Only Name") + tip = _build_tooltip(p) + assert tip == "Only Name" + + +class TestPlayerTableModel: + """PlayerTableModel tests.""" + + def test_empty(self, qapp: QApplication) -> None: + model = PlayerTableModel() + assert model.rowCount() == 0 + assert model.columnCount() == len(["Name", "Age", "CA", "PA"]) + len( + ALL_VISIBLE_ATTRS, + ) + + def test_set_players(self, qapp: QApplication) -> None: + model = PlayerTableModel() + model.set_players([_make_player()]) + assert model.rowCount() == 1 + + def test_data_invalid_index(self, qapp: QApplication) -> None: + model = PlayerTableModel() + assert model.data(QModelIndex()) is None + + def test_data_row_out_of_range(self, qapp: QApplication) -> None: + model = PlayerTableModel() + model.set_players([_make_player()]) + idx = model.index(5, 0) + assert model.data(idx) is None + + def test_data_name(self, qapp: QApplication) -> None: + model = PlayerTableModel() + model.set_players([_make_player(name="Alice")]) + idx = model.index(0, 0) + assert model.data(idx) == "Alice" + + def test_data_age(self, qapp: QApplication) -> None: + model = PlayerTableModel() + model.set_players([_make_player(date_of_birth="1995-06-29")]) + idx = model.index(0, 1) + val = model.data(idx) + assert isinstance(val, int) + assert val > 0 + + def test_data_age_zero(self, qapp: QApplication) -> None: + model = PlayerTableModel() + model.set_players([_make_player(date_of_birth="")]) + idx = model.index(0, 1) + assert model.data(idx) == "" + + def test_data_ca(self, qapp: QApplication) -> None: + model = PlayerTableModel() + model.set_players([_make_player(current_ability=170)]) + idx = model.index(0, 2) + assert model.data(idx) == 170 + + def test_data_ca_zero(self, qapp: QApplication) -> None: + model = PlayerTableModel() + model.set_players([_make_player(current_ability=0)]) + idx = model.index(0, 2) + assert model.data(idx) == "" + + def test_data_pa(self, qapp: QApplication) -> None: + model = PlayerTableModel() + model.set_players([_make_player(potential_ability=185)]) + idx = model.index(0, 3) + assert model.data(idx) == 185 + + def test_data_pa_zero(self, qapp: QApplication) -> None: + model = PlayerTableModel() + model.set_players([_make_player(potential_ability=0)]) + idx = model.index(0, 3) + assert model.data(idx) == "" + + def test_data_attribute(self, qapp: QApplication) -> None: + p = _make_player(attributes={"Corners": 15}) + model = PlayerTableModel() + model.set_players([p]) + # Corners is first attr after fixed cols → col 4. + idx = model.index(0, 4) + assert model.data(idx) == 15 + + def test_data_attribute_zero(self, qapp: QApplication) -> None: + model = PlayerTableModel() + model.set_players([_make_player()]) + idx = model.index(0, 4) + assert model.data(idx) == "" + + def test_background_role_attr(self, qapp: QApplication) -> None: + p = _make_player(attributes={"Corners": 15}) + model = PlayerTableModel() + model.set_players([p]) + idx = model.index(0, 4) + bg = model.data(idx, Qt.ItemDataRole.BackgroundRole) + assert bg is not None + + def test_background_role_no_attr(self, qapp: QApplication) -> None: + model = PlayerTableModel() + model.set_players([_make_player()]) + idx = model.index(0, 4) + bg = model.data(idx, Qt.ItemDataRole.BackgroundRole) + assert bg is None + + def test_background_role_fixed_col(self, qapp: QApplication) -> None: + model = PlayerTableModel() + model.set_players([_make_player()]) + idx = model.index(0, 0) + assert model.data(idx, Qt.ItemDataRole.BackgroundRole) is None + + def test_tooltip_name(self, qapp: QApplication) -> None: + p = _make_player(name="TipTest", club="MyClub") + model = PlayerTableModel() + model.set_players([p]) + idx = model.index(0, 0) + tip = model.data(idx, Qt.ItemDataRole.ToolTipRole) + assert "TipTest" in tip + assert "MyClub" in tip + + def test_tooltip_ca(self, qapp: QApplication) -> None: + model = PlayerTableModel() + model.set_players([_make_player()]) + idx = model.index(0, 2) + tip = model.data(idx, Qt.ItemDataRole.ToolTipRole) + assert "Current Ability" in tip + + def test_tooltip_pa(self, qapp: QApplication) -> None: + model = PlayerTableModel() + model.set_players([_make_player()]) + idx = model.index(0, 3) + tip = model.data(idx, Qt.ItemDataRole.ToolTipRole) + assert "Potential Ability" in tip + + def test_tooltip_other(self, qapp: QApplication) -> None: + model = PlayerTableModel() + model.set_players([_make_player()]) + idx = model.index(0, 4) + assert model.data(idx, Qt.ItemDataRole.ToolTipRole) is None + + def test_alignment_non_name(self, qapp: QApplication) -> None: + model = PlayerTableModel() + model.set_players([_make_player()]) + idx = model.index(0, 2) + align = model.data(idx, Qt.ItemDataRole.TextAlignmentRole) + assert align == Qt.AlignmentFlag.AlignCenter + + def test_alignment_name(self, qapp: QApplication) -> None: + model = PlayerTableModel() + model.set_players([_make_player()]) + idx = model.index(0, 0) + assert model.data(idx, Qt.ItemDataRole.TextAlignmentRole) is None + + def test_unsupported_role(self, qapp: QApplication) -> None: + model = PlayerTableModel() + model.set_players([_make_player()]) + idx = model.index(0, 0) + assert model.data(idx, Qt.ItemDataRole.DecorationRole) is None + + def test_header_display(self, qapp: QApplication) -> None: + model = PlayerTableModel() + h = model.headerData(0, Qt.Orientation.Horizontal) + assert h == "Name" + h = model.headerData(2, Qt.Orientation.Horizontal) + assert h == "CA" + + def test_header_tooltip(self, qapp: QApplication) -> None: + model = PlayerTableModel() + tip = model.headerData( + 2, + Qt.Orientation.Horizontal, + Qt.ItemDataRole.ToolTipRole, + ) + assert "Current Ability" in tip + + def test_header_tooltip_attr(self, qapp: QApplication) -> None: + model = PlayerTableModel() + tip = model.headerData( + 4, + Qt.Orientation.Horizontal, + Qt.ItemDataRole.ToolTipRole, + ) + assert tip == "Corners" + + def test_header_vertical(self, qapp: QApplication) -> None: + model = PlayerTableModel() + assert model.headerData(0, Qt.Orientation.Vertical) is None + + def test_sort_by_name(self, qapp: QApplication) -> None: + model = PlayerTableModel() + model.set_players( + [ + _make_player(name="Zeta"), + _make_player(name="Alpha"), + ] + ) + model.sort(0, Qt.SortOrder.AscendingOrder) + assert model.data(model.index(0, 0)) == "Alpha" + + def test_sort_by_age(self, qapp: QApplication) -> None: + model = PlayerTableModel() + model.set_players( + [ + _make_player(date_of_birth="1990-01-01"), + _make_player(date_of_birth="2000-01-01"), + ] + ) + model.sort(1, Qt.SortOrder.DescendingOrder) + # Older player (1990) has higher age → first in descending. + older_age = model.data(model.index(0, 1)) + younger_age = model.data(model.index(1, 1)) + assert older_age > younger_age + + def test_sort_by_ca(self, qapp: QApplication) -> None: + model = PlayerTableModel() + model.set_players( + [ + _make_player(current_ability=100), + _make_player(current_ability=200), + ] + ) + model.sort(2, Qt.SortOrder.DescendingOrder) + assert model.data(model.index(0, 2)) == 200 + + def test_sort_by_pa(self, qapp: QApplication) -> None: + model = PlayerTableModel() + model.set_players( + [ + _make_player(potential_ability=100), + _make_player(potential_ability=200), + ] + ) + model.sort(3, Qt.SortOrder.DescendingOrder) + assert model.data(model.index(0, 3)) == 200 + + def test_sort_by_attr(self, qapp: QApplication) -> None: + model = PlayerTableModel() + model.set_players( + [ + _make_player(attributes={"Corners": 5}), + _make_player(attributes={"Corners": 20}), + ] + ) + model.sort(4, Qt.SortOrder.DescendingOrder) + assert model.data(model.index(0, 4)) == 20 + + def test_get_player(self, qapp: QApplication) -> None: + model = PlayerTableModel() + p = _make_player(name="Target") + model.set_players([p]) + assert model.get_player(0) is p + + def test_get_player_out_of_range( + self, + qapp: QApplication, + ) -> None: + model = PlayerTableModel() + assert model.get_player(5) is None + + def test_get_player_negative( + self, + qapp: QApplication, + ) -> None: + model = PlayerTableModel() + assert model.get_player(-1) is None + + def test_data_stale_row(self, qapp: QApplication) -> None: + """Row >= len(players) via createIndex (line 205).""" + model = PlayerTableModel() + model.set_players([_make_player()]) + idx = model.createIndex(5, 0) + assert model.data(idx) is None + + def test_display_data_fallthrough(self, qapp: QApplication) -> None: + """_display_data returns None for impossible col (line 239).""" + model = PlayerTableModel() + p = _make_player() + model.set_players([p]) + # Call _display_data directly with col=-1 (not a fixed col) + assert model._display_data(p, 0, -1) is None + + def test_sort_invalid_column(self, qapp: QApplication) -> None: + """sort() with col returning None key_fn (lines 304, 314).""" + model = PlayerTableModel() + model.set_players([_make_player()]) + # col=-1 returns None from _sort_key → sort returns early + model.sort(-1) + + +class TestLoadingOverlay: + """LoadingOverlay tests.""" + + def test_update_progress(self, qapp: QApplication) -> None: + overlay = LoadingOverlay() + overlay.update_progress("Testing...", 50, "~5s remaining") + assert overlay._stage.text() == "Testing..." + assert overlay._progress.value() == 50 + assert overlay._eta.text() == "~5s remaining" + + def test_paint_event(self, qapp: QApplication) -> None: + overlay = LoadingOverlay() + overlay.resize(200, 200) + overlay.show() + # Call paintEvent directly to ensure coverage. + event = QPaintEvent(overlay.rect()) + overlay.paintEvent(event) + + +class TestFilterPanel: + """FilterPanel tests.""" + + def test_get_filters_empty(self, qapp: QApplication) -> None: + panel = FilterPanel("Test", ["Pace", "Stamina"]) + assert panel.get_filters() == {} + + def test_get_filters_nonzero(self, qapp: QApplication) -> None: + panel = FilterPanel("Test", ["Pace", "Stamina"]) + panel.sliders["Pace"].setValue(10) + result = panel.get_filters() + assert result == {"Pace": 10} + + def test_reset(self, qapp: QApplication) -> None: + panel = FilterPanel("Test", ["Pace"]) + panel.sliders["Pace"].setValue(15) + panel.reset() + assert panel.sliders["Pace"].value() == 0 + + +class TestWeightPanel: + """WeightPanel tests.""" + + def test_get_weights_empty(self, qapp: QApplication) -> None: + panel = WeightPanel() + assert panel.get_weights() == {} + + def test_get_weights_nonzero(self, qapp: QApplication) -> None: + panel = WeightPanel() + panel.combos["Pace"].setValue(5) + result = panel.get_weights() + assert result == {"Pace": 5.0} + + +class TestCompareDialog: + """CompareDialog tests.""" + + def test_creation(self, qapp: QApplication) -> None: + players = [ + _make_player(name="P1", current_ability=170), + _make_player(name="P2", current_ability=180), + ] + dlg = CompareDialog(players) + assert dlg.windowTitle() == "Compare Players" + + def test_attr_bolding(self, qapp: QApplication) -> None: + """Best attribute value is bolded (lines 607-611).""" + players = [ + _make_player( + name="P1", + attributes={"Corners": 18, "Pace": 10}, + ), + _make_player( + name="P2", + attributes={"Corners": 12, "Pace": 15}, + ), + ] + dlg = CompareDialog(players) + assert dlg.windowTitle() == "Compare Players" + + +@pytest.fixture +def main_window(qapp: QApplication) -> MainWindow: + """Create MainWindow with non-existent default DB and drain timers.""" + with patch( + "python_pkg.fm24_searcher.gui.DEFAULT_PEOPLE_DB", + Path("/nonexistent/path.dat"), + ): + win = MainWindow() + QApplication.processEvents() + return win + + +class TestMainWindow: + """MainWindow tests.""" + + def test_creation_no_db( + self, + main_window: MainWindow, + ) -> None: + assert main_window.windowTitle() == "FM24 Database Searcher" + + def test_search_empty( + self, + main_window: MainWindow, + ) -> None: + main_window.all_players = [_make_player(name="Alice")] + main_window._do_search() + assert len(main_window.filtered_players) == 1 + + def test_search_with_query( + self, + main_window: MainWindow, + ) -> None: + main_window.all_players = [ + _make_player(name="Alice"), + _make_player(name="Bob"), + ] + main_window.search_input.setText("alice") + main_window._do_search() + assert len(main_window.filtered_players) == 1 + assert main_window.filtered_players[0].name == "Alice" + + def test_reset_filters( + self, + main_window: MainWindow, + ) -> None: + main_window.all_players = [_make_player()] + main_window.search_input.setText("test") + main_window._reset_filters() + assert main_window.search_input.text() == "" + assert len(main_window.filtered_players) == 1 + + def test_apply_filters_with_weights( + self, + main_window: MainWindow, + ) -> None: + p1 = _make_player( + name="Good", + attributes={"Pace": 18, "Stamina": 15}, + ) + p2 = _make_player( + name="Bad", + attributes={"Pace": 5, "Stamina": 5}, + ) + main_window.all_players = [p2, p1] + main_window.weight_panel.combos["Pace"].setValue(5) + main_window._apply_filters() + assert main_window.filtered_players[0].name == "Good" + + def test_apply_filters_no_weights( + self, + main_window: MainWindow, + ) -> None: + p1 = _make_player(name="High", current_ability=190) + p2 = _make_player(name="Low", current_ability=100) + main_window.all_players = [p2, p1] + main_window._apply_filters() + assert main_window.filtered_players[0].name == "High" + + def test_apply_filters_min_ca( + self, + main_window: MainWindow, + ) -> None: + main_window.all_players = [ + _make_player(current_ability=100), + _make_player(current_ability=200), + ] + main_window.min_ca.setText("150") + main_window._apply_filters() + assert len(main_window.filtered_players) == 1 + + def test_on_load_finished_first( + self, + main_window: MainWindow, + ) -> None: + players = [_make_player()] + main_window._on_load_finished(players) + assert len(main_window.all_players) == 1 + + def test_on_load_finished_merge( + self, + main_window: MainWindow, + ) -> None: + main_window.all_players = [_make_player(name="Existing")] + main_window._on_load_finished([_make_player(name="New")]) + names = {p.name for p in main_window.all_players} + assert "Existing" in names + assert "New" in names + + def test_on_load_error( + self, + main_window: MainWindow, + ) -> None: + with patch( + "python_pkg.fm24_searcher.gui.QMessageBox.critical", + ) as mock_critical: + main_window._on_load_error("test error") + mock_critical.assert_called_once() + + def test_on_load_progress_no_eta( + self, + main_window: MainWindow, + ) -> None: + main_window._load_start = 0.0 + main_window._on_load_progress("Stage", 3) + + def test_on_load_progress_with_eta( + self, + main_window: MainWindow, + ) -> None: + import time + + main_window._load_start = time.monotonic() - 5.0 + main_window._on_load_progress("Stage", 50) + + def test_overlay_show_hide( + self, + main_window: MainWindow, + ) -> None: + main_window._show_overlay() + assert not main_window._overlay.isHidden() + main_window._hide_overlay() + assert main_window._overlay.isHidden() + + def test_resize_event( + self, + main_window: MainWindow, + ) -> None: + main_window.resize(800, 600) + + def test_search_timer( + self, + main_window: MainWindow, + ) -> None: + main_window._on_search_changed() + assert main_window._search_timer.isActive() + + def test_compare_too_few( + self, + main_window: MainWindow, + ) -> None: + with patch( + "python_pkg.fm24_searcher.gui.QMessageBox.information", + ): + main_window._compare_selected() + + def test_load_html_cancel( + self, + main_window: MainWindow, + ) -> None: + with patch( + "python_pkg.fm24_searcher.gui.QFileDialog.getOpenFileName", + return_value=("", ""), + ): + main_window._load_html() + assert len(main_window.all_players) == 0 + + def test_load_html_success( + self, + main_window: MainWindow, + tmp_path: Path, + ) -> None: + html_file = tmp_path / "test.html" + html_file.write_text( + "
Name
HTMLPlayer
", + encoding="utf-8", + ) + with patch( + "python_pkg.fm24_searcher.gui.QFileDialog.getOpenFileName", + return_value=(str(html_file), ""), + ): + main_window._load_html() + assert any(p.name == "HTMLPlayer" for p in main_window.all_players) + + def test_load_html_error( + self, + main_window: MainWindow, + ) -> None: + with ( + patch( + "python_pkg.fm24_searcher.gui.QFileDialog.getOpenFileName", + return_value=("/bad/path.html", ""), + ), + patch( + "python_pkg.fm24_searcher.gui.QMessageBox.critical", + ) as mock_crit, + ): + main_window._load_html() + mock_crit.assert_called_once() + + def test_load_html_merge_existing( + self, + main_window: MainWindow, + tmp_path: Path, + ) -> None: + main_window.all_players = [_make_player(name="Existing")] + html_file = tmp_path / "test.html" + html_file.write_text( + "
Name
New
", + encoding="utf-8", + ) + with patch( + "python_pkg.fm24_searcher.gui.QFileDialog.getOpenFileName", + return_value=(str(html_file), ""), + ): + main_window._load_html() + names = {p.name for p in main_window.all_players} + assert "Existing" in names + assert "New" in names + + def test_load_binary_db_cancel( + self, + main_window: MainWindow, + ) -> None: + with patch( + "python_pkg.fm24_searcher.gui.QFileDialog.getOpenFileName", + return_value=("", ""), + ): + main_window._load_binary_db() + + def test_load_binary_db_with_file( + self, + main_window: MainWindow, + tmp_path: Path, + ) -> None: + """_load_binary_db when a file is selected (line 870).""" + fake_path = tmp_path / "fake.dat" + with ( + patch( + "python_pkg.fm24_searcher.gui.QFileDialog.getOpenFileName", + return_value=(str(fake_path), ""), + ), + patch.object( + main_window, + "_load_binary_db_from_path", + ) as mock_load, + ): + main_window._load_binary_db() + mock_load.assert_called_once_with(fake_path) + + def test_load_binary_db_from_path_success( + self, + main_window: MainWindow, + ) -> None: + """_load_binary_db_from_path thread emits done_sig (lines 877-901).""" + fake_players = [_make_player()] + with patch( + "python_pkg.fm24_searcher.gui.parse_people_db", + return_value=fake_players, + ): + main_window._load_binary_db_from_path(Path("/fake.dat")) + main_window._load_thread.join(timeout=5) + QApplication.processEvents() + assert len(main_window.all_players) >= 1 + + def test_load_binary_db_from_path_error( + self, + main_window: MainWindow, + ) -> None: + """_load_binary_db_from_path thread emits error_sig on failure.""" + with ( + patch( + "python_pkg.fm24_searcher.gui.parse_people_db", + side_effect=OSError("bad file"), + ), + patch( + "python_pkg.fm24_searcher.gui.QMessageBox.critical", + ) as mock_crit, + ): + main_window._load_binary_db_from_path(Path("/bad.dat")) + main_window._load_thread.join(timeout=5) + QApplication.processEvents() + mock_crit.assert_called_once() + + def test_auto_load_when_db_exists( + self, + qapp: QApplication, + ) -> None: + """_auto_load calls _load_binary_db_from_path (line 853).""" + with patch( + "python_pkg.fm24_searcher.gui.DEFAULT_PEOPLE_DB", + Path("/fake/path.dat"), + ): + win = MainWindow() + with ( + patch.object( + win, + "_load_binary_db_from_path", + ) as mock_load, + patch( + "python_pkg.fm24_searcher.gui.DEFAULT_PEOPLE_DB", + ) as mock_db, + ): + mock_db.exists.return_value = True + win._auto_load() + mock_load.assert_called_once() + QApplication.processEvents() + + def test_resize_event_with_central_widget( + self, + main_window: MainWindow, + ) -> None: + """resizeEvent repositions overlay (lines 834-837).""" + from PyQt6.QtCore import QSize + from PyQt6.QtGui import QResizeEvent + + event = QResizeEvent(QSize(1000, 700), QSize(800, 600)) + main_window.resizeEvent(event) + + def test_show_overlay_no_central_widget( + self, + main_window: MainWindow, + ) -> None: + """_show_overlay when centralWidget is None (branch 841→843).""" + with patch.object( + main_window, + "centralWidget", + return_value=None, + ): + main_window._show_overlay() + assert not main_window._overlay.isHidden() + + def test_resize_event_no_central_widget( + self, + main_window: MainWindow, + ) -> None: + """resizeEvent when centralWidget is None (branch 836→exit).""" + from PyQt6.QtCore import QSize + from PyQt6.QtGui import QResizeEvent + + event = QResizeEvent(QSize(500, 300), QSize(400, 200)) + with patch.object( + main_window, + "centralWidget", + return_value=None, + ): + main_window.resizeEvent(event) + + def test_create_menu_null_menubar( + self, + main_window: MainWindow, + ) -> None: + """_create_menu returns early when menuBar is None (line 655).""" + with patch.object( + main_window, + "menuBar", + return_value=None, + ): + main_window._create_menu() + + def test_create_menu_null_file_menu( + self, + main_window: MainWindow, + ) -> None: + """_create_menu returns early when addMenu is None (line 659).""" + mock_bar = MagicMock() + mock_bar.addMenu.return_value = None + with patch.object( + main_window, + "menuBar", + return_value=mock_bar, + ): + main_window._create_menu() + + def test_create_table_null_hdr( + self, + qapp: QApplication, + ) -> None: + """Branch 798→813: horizontalHeader returns None.""" + with ( + patch( + "python_pkg.fm24_searcher.gui.DEFAULT_PEOPLE_DB", + Path("/nonexistent/path.dat"), + ), + patch( + "python_pkg.fm24_searcher.gui.QTableView.horizontalHeader", + return_value=None, + ), + ): + win = MainWindow() + QApplication.processEvents() + assert win.player_table is not None + + def test_create_table_null_vhdr( + self, + qapp: QApplication, + ) -> None: + """Branch 814→817: verticalHeader returns None.""" + with ( + patch( + "python_pkg.fm24_searcher.gui.DEFAULT_PEOPLE_DB", + Path("/nonexistent/path.dat"), + ), + patch( + "python_pkg.fm24_searcher.gui.QTableView.verticalHeader", + return_value=None, + ), + ): + win = MainWindow() + QApplication.processEvents() + assert win.player_table is not None + + def test_compare_selected_null_selection_model( + self, + main_window: MainWindow, + ) -> None: + """_compare_selected returns when sel is None (line 1064).""" + with patch.object( + main_window.player_table, + "selectionModel", + return_value=None, + ): + main_window._compare_selected() + + def test_compare_selected_with_players( + self, + main_window: MainWindow, + ) -> None: + """_compare_selected creates dialog (lines 1073-1083).""" + p1 = _make_player(name="Player1", current_ability=170) + p2 = _make_player(name="Player2", current_ability=180) + main_window.all_players = [p1, p2] + main_window._model.set_players([p1, p2]) + # Select both rows. + sel = main_window.player_table.selectionModel() + idx0 = main_window._model.index(0, 0) + idx1 = main_window._model.index(1, 0) + from PyQt6.QtCore import QItemSelectionModel + + sel.select( + idx0, + QItemSelectionModel.SelectionFlag.Select + | QItemSelectionModel.SelectionFlag.Rows, + ) + sel.select( + idx1, + QItemSelectionModel.SelectionFlag.Select + | QItemSelectionModel.SelectionFlag.Rows, + ) + with patch( + "python_pkg.fm24_searcher.gui.CompareDialog.exec", + ): + main_window._compare_selected() + + def test_compare_selected_get_player_none( + self, + main_window: MainWindow, + ) -> None: + """_compare_selected when get_player returns None (1079, 1081).""" + p1 = _make_player(name="P1") + p2 = _make_player(name="P2") + main_window.all_players = [p1, p2] + main_window._model.set_players([p1, p2]) + sel = main_window.player_table.selectionModel() + idx0 = main_window._model.index(0, 0) + idx1 = main_window._model.index(1, 0) + from PyQt6.QtCore import QItemSelectionModel + + sel.select( + idx0, + QItemSelectionModel.SelectionFlag.Select + | QItemSelectionModel.SelectionFlag.Rows, + ) + sel.select( + idx1, + QItemSelectionModel.SelectionFlag.Select + | QItemSelectionModel.SelectionFlag.Rows, + ) + with patch.object( + main_window._model, + "get_player", + return_value=None, + ): + main_window._compare_selected() + + def test_apply_filters_meta( + self, + main_window: MainWindow, + ) -> None: + main_window.all_players = [ + _make_player( + name="Target", + position="AMC", + nationality="Spain", + club="Madrid", + ), + ] + main_window.pos_filter.setText("AMC") + main_window.nat_filter.setText("Spain") + main_window.club_filter.setText("Madrid") + main_window._apply_filters() + assert len(main_window.filtered_players) == 1 + + def test_apply_filters_with_search( + self, + main_window: MainWindow, + ) -> None: + main_window.all_players = [ + _make_player(name="Alice"), + _make_player(name="Bob"), + ] + main_window.search_input.setText("alice") + main_window._apply_filters() + assert len(main_window.filtered_players) == 1 + + def test_apply_filters_attr_filter( + self, + main_window: MainWindow, + ) -> None: + main_window.all_players = [ + _make_player( + name="Fast", + attributes={"Pace": 18}, + ), + _make_player(name="Slow", attributes={"Pace": 3}), + ] + main_window.phys_filter.sliders["Pace"].setValue(10) + main_window._apply_filters() + assert len(main_window.filtered_players) == 1 + + def test_info_banner_visible_no_data( + self, + main_window: MainWindow, + ) -> None: + """Info banner is visible when no attr data loaded.""" + main_window.all_players = [] + main_window._update_data_status(0) + assert not main_window._info_banner.isHidden() + + def test_info_banner_hidden_with_ca( + self, + main_window: MainWindow, + ) -> None: + """Info banner is hidden when CA data is available.""" + main_window.all_players = [ + _make_player(current_ability=170), + ] + main_window._update_data_status(1) + assert main_window._info_banner.isHidden() + + def test_info_banner_hidden_with_attrs( + self, + main_window: MainWindow, + ) -> None: + """Info banner is hidden when attributes loaded.""" + main_window.all_players = [ + _make_player( + current_ability=0, + attributes={"Pace": 15}, + ), + ] + main_window._update_data_status(1) + assert main_window._info_banner.isHidden() + + def test_update_data_status_no_data( + self, + main_window: MainWindow, + ) -> None: + """Status shows import hint when no attrs.""" + main_window.all_players = [ + _make_player( + current_ability=0, + potential_ability=0, + ), + ] + main_window._update_data_status(1) + msg = main_window.status.currentMessage() + assert "import HTML" in msg + + def test_update_data_status_with_data( + self, + main_window: MainWindow, + ) -> None: + """Status shows CA/Attrs counts when data present.""" + main_window.all_players = [ + _make_player( + current_ability=170, + attributes={"Pace": 18}, + ), + ] + main_window._update_data_status(1) + msg = main_window.status.currentMessage() + assert "CA:" in msg + assert "Attrs:" in msg + + def test_show_import_guide( + self, + main_window: MainWindow, + ) -> None: + """_show_import_guide opens a message box.""" + with patch( + "python_pkg.fm24_searcher.gui.QMessageBox.information", + ) as mock_info: + main_window._show_import_guide() + mock_info.assert_called_once() + args = mock_info.call_args + assert "Import" in args[0][1] + + def test_import_guide_constant(self) -> None: + """_IMPORT_GUIDE contains key instructions.""" + assert "Ctrl+P" in _IMPORT_GUIDE + assert "HTML" in _IMPORT_GUIDE + assert "CA" in _IMPORT_GUIDE + + def test_create_menu_null_help_menu( + self, + main_window: MainWindow, + ) -> None: + """_create_menu handles None help menu.""" + mock_bar = MagicMock() + file_menu = MagicMock() + mock_bar.addMenu.side_effect = [ + file_menu, + None, + ] + with patch.object( + main_window, + "menuBar", + return_value=mock_bar, + ): + main_window._create_menu() + + +class TestMainEntry: + """main() entry point test.""" + + def test_main_calls_app(self) -> None: + with ( + patch( + "python_pkg.fm24_searcher.gui.QApplication", + ) as mock_app_cls, + patch( + "python_pkg.fm24_searcher.gui.MainWindow", + ) as mock_win_cls, + patch("sys.exit"), + ): + mock_app = MagicMock() + mock_app_cls.return_value = mock_app + mock_win = MagicMock() + mock_win_cls.return_value = mock_win + main() + mock_app_cls.assert_called_once() + mock_win.show.assert_called_once() + + def test_dunder_main_import(self) -> None: + """Cover __main__.py line 2 (the import).""" + import importlib + + mod = importlib.import_module("python_pkg.fm24_searcher.__main__") + importlib.reload(mod) diff --git a/python_pkg/fm24_searcher/tests/test_html_parser.py b/python_pkg/fm24_searcher/tests/test_html_parser.py new file mode 100644 index 0000000..a860aec --- /dev/null +++ b/python_pkg/fm24_searcher/tests/test_html_parser.py @@ -0,0 +1,402 @@ +"""Tests for python_pkg.fm24_searcher.html_parser.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from python_pkg.fm24_searcher.html_parser import ( + _extract_tables, + _normalize_header, + _strip_html, + merge_players, + parse_html_export, +) +from python_pkg.fm24_searcher.models import Player + +if TYPE_CHECKING: + from pathlib import Path + + +class TestStripHtml: + """_strip_html tests.""" + + def test_removes_tags(self) -> None: + assert _strip_html("bold") == "bold" + + def test_decodes_entities(self) -> None: + assert _strip_html("& <") == "& <" + + def test_collapses_whitespace(self) -> None: + assert _strip_html(" hello world ") == "hello world" + + def test_nested_tags(self) -> None: + assert _strip_html("
text
") == "text" + + +class TestNormalizeHeader: + """_normalize_header tests.""" + + def test_direct_map(self) -> None: + assert _normalize_header("cor") == "Corners" + assert _normalize_header("fin") == "Finishing" + + def test_full_name_lower(self) -> None: + assert _normalize_header("Acceleration") == "Acceleration" + + def test_truncated_3char(self) -> None: + assert _normalize_header("crossing") == "Crossing" + + def test_unknown(self) -> None: + assert _normalize_header("xyz") is None + + def test_truncated_prefix_only(self) -> None: + """Full string not in map but first 3 chars match (line 113).""" + assert _normalize_header("corxxx") == "Corners" + + def test_html_in_header(self) -> None: + assert _normalize_header("cor") == "Corners" + + def test_goalkeeper_attr(self) -> None: + assert _normalize_header("han") == "Handling" + assert _normalize_header("ref") == "Reflexes" + + +class TestExtractTables: + """_extract_tables tests.""" + + def test_single_table(self) -> None: + html = ( + "" + "
NameAge
John25
" + ) + tables = _extract_tables(html) + assert len(tables) == 1 + assert tables[0][0] == ["Name", "Age"] + assert tables[0][1] == ["John", "25"] + + def test_multiple_tables(self) -> None: + html = "
A
B
" + tables = _extract_tables(html) + assert len(tables) == 2 + + def test_empty_table_filtered(self) -> None: + html = "
X
" + tables = _extract_tables(html) + assert len(tables) == 1 + + def test_nested_html_stripped(self) -> None: + html = "
Bold
" + tables = _extract_tables(html) + assert tables[0][0] == ["Bold"] + + def test_row_without_cells(self) -> None: + """Row with no cells is skipped (branch 140→135).""" + html = "
valid
" + tables = _extract_tables(html) + assert len(tables) == 1 + assert len(tables[0]) == 1 + assert tables[0][0] == ["valid"] + + +class TestParseHtmlExport: + """parse_html_export tests.""" + + def _write_html(self, tmp_path: Path, content: str) -> Path: + p = tmp_path / "export.html" + p.write_text(content, encoding="utf-8") + return p + + def test_basic_table(self, tmp_path: Path) -> None: + html = ( + "" + "" + "" + "" + "" + "
NameAgeCAPAPacSta
John Smith251701801815
" + ) + p = self._write_html(tmp_path, html) + players = parse_html_export(p) + assert len(players) == 1 + assert players[0].name == "John Smith" + assert players[0].current_ability == 170 + assert players[0].potential_ability == 180 + assert players[0].attributes["Pace"] == 18 + assert players[0].attributes["Stamina"] == 15 + assert players[0].source == "html" + + def test_no_name_column(self, tmp_path: Path) -> None: + html = ( + "" + "" + "" + "
CAPA
170180
" + ) + p = self._write_html(tmp_path, html) + assert parse_html_export(p) == [] + + def test_table_too_few_rows(self, tmp_path: Path) -> None: + html = "
Name
" + p = self._write_html(tmp_path, html) + assert parse_html_export(p) == [] + + def test_row_shorter_than_name_col(self, tmp_path: Path) -> None: + html = ( + "
AName
only_one
" + ) + p = self._write_html(tmp_path, html) + assert parse_html_export(p) == [] + + def test_empty_name_skipped(self, tmp_path: Path) -> None: + html = ( + "" + "" + "" + "" + "
Name
Valid
" + ) + p = self._write_html(tmp_path, html) + players = parse_html_export(p) + assert len(players) == 1 + assert players[0].name == "Valid" + + def test_invalid_ca_pa(self, tmp_path: Path) -> None: + html = ( + "" + "" + "" + "
NameCAPA
Testabcxyz
" + ) + p = self._write_html(tmp_path, html) + players = parse_html_export(p) + assert players[0].current_ability == 0 + + def test_attr_col_beyond_row_length(self, tmp_path: Path) -> None: + """Attribute col index >= row length (branch 240→239).""" + html = ( + "" + "" + "" + "
NameCorPac
Player15
" + ) + p = self._write_html(tmp_path, html) + players = parse_html_export(p) + assert len(players) == 1 + assert players[0].attributes.get("Corners") == 15 + assert "Pace" not in players[0].attributes + assert players[0].potential_ability == 0 + + def test_club_nat_pos_value_wage(self, tmp_path: Path) -> None: + html = ( + "" + "" + "" + "" + "" + "
NameClubNatPositionValueWage
JohnMadridSpainAMC€50M€200K
" + ) + p = self._write_html(tmp_path, html) + players = parse_html_export(p) + assert players[0].club == "Madrid" + assert players[0].nationality == "Spain" + assert players[0].position == "AMC" + assert players[0].value == "€50M" + assert players[0].wage == "€200K" + + def test_range_format(self, tmp_path: Path) -> None: + html = ( + "" + "" + "" + "
NamePac
Test12-16
" + ) + p = self._write_html(tmp_path, html) + players = parse_html_export(p) + assert players[0].attributes["Pace"] == 12 + + def test_attr_out_of_range(self, tmp_path: Path) -> None: + html = ( + "" + "" + "" + "
NamePac
Test25
" + ) + p = self._write_html(tmp_path, html) + players = parse_html_export(p) + assert "Pace" not in players[0].attributes + + def test_attr_not_int(self, tmp_path: Path) -> None: + html = ( + "" + "" + "" + "
NamePac
TestN/A
" + ) + p = self._write_html(tmp_path, html) + players = parse_html_export(p) + assert "Pace" not in players[0].attributes + + def test_gk_attributes(self, tmp_path: Path) -> None: + html = ( + "" + "" + "" + "
NameHanRef
GK Test1518
" + ) + p = self._write_html(tmp_path, html) + players = parse_html_export(p) + assert players[0].gk_attributes["Handling"] == 15 + assert players[0].gk_attributes["Reflexes"] == 18 + + def test_player_header_name(self, tmp_path: Path) -> None: + html = "
Player
Alt Name
" + p = self._write_html(tmp_path, html) + players = parse_html_export(p) + assert players[0].name == "Alt Name" + + def test_club_header_team(self, tmp_path: Path) -> None: + html = ( + "" + "" + "" + "
NameTeam
TestMyClub
" + ) + p = self._write_html(tmp_path, html) + assert parse_html_export(p)[0].club == "MyClub" + + def test_ability_header(self, tmp_path: Path) -> None: + html = ( + "" + "" + "" + "" + "
NameAbilityPotential
T150180
" + ) + p = self._write_html(tmp_path, html) + players = parse_html_export(p) + assert players[0].current_ability == 150 + assert players[0].potential_ability == 180 + + def test_nationality_header(self, tmp_path: Path) -> None: + html = ( + "" + "" + "" + "
NameNationality
PBrazil
" + ) + p = self._write_html(tmp_path, html) + assert parse_html_export(p)[0].nationality == "Brazil" + + def test_pos_header(self, tmp_path: Path) -> None: + html = ( + "" + "" + "" + "
NamePos
PGK
" + ) + p = self._write_html(tmp_path, html) + assert parse_html_export(p)[0].position == "GK" + + def test_val_header(self, tmp_path: Path) -> None: + html = ( + "" + "" + "" + "
NameVal
P€10M
" + ) + p = self._write_html(tmp_path, html) + assert parse_html_export(p)[0].value == "€10M" + + +class TestMergePlayers: + """merge_players tests.""" + + def test_match_by_name(self) -> None: + bp = Player( + name="John Smith", + date_of_birth="1995-06-29", + source="binary", + ) + hp = Player( + name="John Smith", + current_ability=170, + potential_ability=185, + club="Madrid", + nationality="Spain", + position="AMC", + value="€50M", + wage="€200K", + attributes={"Pace": 18}, + gk_attributes={"Handling": 15}, + source="html", + ) + result = merge_players([bp], [hp]) + assert len(result) == 1 + merged = result[0] + assert merged.source == "merged" + assert merged.date_of_birth == "1995-06-29" + assert merged.current_ability == 170 + assert merged.potential_ability == 185 + assert merged.club == "Madrid" + assert merged.nationality == "Spain" + assert merged.position == "AMC" + assert merged.value == "€50M" + assert merged.wage == "€200K" + assert merged.attributes["Pace"] == 18 + assert merged.gk_attributes["Handling"] == 15 + + def test_html_only(self) -> None: + hp = Player(name="HTML Only", source="html") + result = merge_players([], [hp]) + assert len(result) == 1 + assert result[0].name == "HTML Only" + + def test_binary_only(self) -> None: + bp = Player(name="Binary Only", source="binary") + result = merge_players([bp], []) + assert len(result) == 1 + assert result[0].name == "Binary Only" + + def test_no_overwrite_when_html_zero(self) -> None: + bp = Player( + name="Test", + current_ability=150, + potential_ability=180, + club="OldClub", + source="binary", + ) + hp = Player( + name="Test", + current_ability=0, + potential_ability=0, + club="", + source="html", + ) + result = merge_players([bp], [hp]) + assert result[0].current_ability == 150 + assert result[0].potential_ability == 180 + assert result[0].club == "OldClub" + + def test_case_insensitive_match(self) -> None: + bp = Player(name="JOHN SMITH", source="binary") + hp = Player( + name="john smith", + attributes={"Pace": 15}, + source="html", + ) + result = merge_players([bp], [hp]) + assert len(result) == 1 + assert result[0].attributes["Pace"] == 15 + + def test_mixed(self) -> None: + bp1 = Player(name="Matched", source="binary") + bp2 = Player(name="Binary Only", source="binary") + hp1 = Player( + name="Matched", + club="Club", + source="html", + ) + hp2 = Player(name="HTML Only", source="html") + result = merge_players([bp1, bp2], [hp1, hp2]) + names = {p.name for p in result} + assert names == {"Matched", "Binary Only", "HTML Only"} diff --git a/python_pkg/fm24_searcher/tests/test_main.py b/python_pkg/fm24_searcher/tests/test_main.py new file mode 100644 index 0000000..f11fd45 --- /dev/null +++ b/python_pkg/fm24_searcher/tests/test_main.py @@ -0,0 +1,36 @@ +"""Tests for python_pkg.fm24_searcher.__main__.""" + +from __future__ import annotations + +from unittest.mock import patch + +import pytest + +from python_pkg.fm24_searcher.__main__ import _main + + +class TestMain: + """__main__ dispatch tests.""" + + def test_dump_mode(self) -> None: + with ( + patch("sys.argv", ["fm24", "--dump", "--db", "/nonexistent"]), + patch( + "python_pkg.fm24_searcher.cli.run_dump", + return_value=0, + ) as mock_dump, + ): + with pytest.raises(SystemExit) as exc_info: + _main() + assert exc_info.value.code == 0 + mock_dump.assert_called_once() + + def test_gui_mode(self) -> None: + with ( + patch("sys.argv", ["fm24"]), + patch( + "python_pkg.fm24_searcher.gui.main", + ) as mock_gui, + ): + _main() + mock_gui.assert_called_once() diff --git a/python_pkg/fm24_searcher/tests/test_models.py b/python_pkg/fm24_searcher/tests/test_models.py new file mode 100644 index 0000000..1318a10 --- /dev/null +++ b/python_pkg/fm24_searcher/tests/test_models.py @@ -0,0 +1,160 @@ +"""Tests for python_pkg.fm24_searcher.models.""" + +from __future__ import annotations + +from python_pkg.fm24_searcher.models import ( + ALL_VISIBLE_ATTRS, + GOALKEEPER_ATTRS, + MENTAL_ATTRS, + PHYSICAL_ATTRS, + TECHNICAL_ATTRS, + Player, +) + + +class TestAttributeLists: + """Attribute list sanity checks.""" + + def test_technical_count(self) -> None: + assert len(TECHNICAL_ATTRS) == 14 + + def test_mental_count(self) -> None: + assert len(MENTAL_ATTRS) == 14 + + def test_physical_count(self) -> None: + assert len(PHYSICAL_ATTRS) == 8 + + def test_goalkeeper_count(self) -> None: + assert len(GOALKEEPER_ATTRS) == 13 + + def test_all_visible_is_concat(self) -> None: + assert ALL_VISIBLE_ATTRS == (TECHNICAL_ATTRS + MENTAL_ATTRS + PHYSICAL_ATTRS) + + +class TestPlayerDefaults: + """Player dataclass default values.""" + + def test_defaults(self) -> None: + p = Player() + assert p.uid == 0 + assert p.name == "" + assert p.date_of_birth == "" + assert p.nationality == "" + assert p.club == "" + assert p.position == "" + assert p.current_ability == 0 + assert p.potential_ability == 0 + assert p.personality == [] + assert p.attributes == {} + assert p.gk_attributes == {} + assert p.value == "" + assert p.wage == "" + assert p.source == "" + + +class TestGetAttr: + """Player.get_attr() method.""" + + def test_present(self) -> None: + p = Player(attributes={"Pace": 18}) + assert p.get_attr("Pace") == 18 + + def test_missing(self) -> None: + p = Player(attributes={"Pace": 18}) + assert p.get_attr("Strength") == 0 + + +class TestWeightedScore: + """Player.weighted_score() method.""" + + def test_basic_score(self) -> None: + p = Player(attributes={"Pace": 18, "Stamina": 12}) + score = p.weighted_score({"Pace": 2.0, "Stamina": 1.0}) + expected = (18 * 2.0 + 12 * 1.0) / (2.0 + 1.0) + assert score == expected + + def test_zero_weight_sum(self) -> None: + p = Player() + assert p.weighted_score({"Pace": 0.0}) == 0.0 + + def test_missing_attrs_ignored(self) -> None: + p = Player(attributes={"Pace": 10}) + score = p.weighted_score({"Pace": 1.0, "Stamina": 1.0}) + # Stamina=0 so val > 0 is False → not counted. + assert score == 10.0 / 1.0 + + def test_empty_weights(self) -> None: + p = Player(attributes={"Pace": 18}) + assert p.weighted_score({}) == 0.0 + + +class TestMatchesFilter: + """Player.matches_filter() method.""" + + def test_no_filters(self) -> None: + p = Player(name="Test") + assert p.matches_filter() is True + + def test_min_attrs_pass(self) -> None: + p = Player(attributes={"Pace": 15, "Stamina": 12}) + assert p.matches_filter(min_attrs={"Pace": 10}) is True + + def test_min_attrs_fail(self) -> None: + p = Player(attributes={"Pace": 5}) + assert p.matches_filter(min_attrs={"Pace": 10}) is False + + def test_min_ca_pass(self) -> None: + p = Player(current_ability=150) + assert p.matches_filter(min_ca=100) is True + + def test_min_ca_fail(self) -> None: + p = Player(current_ability=50) + assert p.matches_filter(min_ca=100) is False + + def test_min_ca_zero_skipped(self) -> None: + p = Player(current_ability=0) + assert p.matches_filter(min_ca=0) is True + + def test_position_filter_pass(self) -> None: + p = Player(position="AMC, MC") + assert p.matches_filter(position_filter="mc") is True + + def test_position_filter_fail(self) -> None: + p = Player(position="DC") + assert p.matches_filter(position_filter="amc") is False + + def test_nationality_filter_pass(self) -> None: + p = Player(nationality="England") + assert p.matches_filter(nationality_filter="eng") is True + + def test_nationality_filter_fail(self) -> None: + p = Player(nationality="France") + assert p.matches_filter(nationality_filter="eng") is False + + def test_club_filter_pass(self) -> None: + p = Player(club="Manchester United") + assert p.matches_filter(club_filter="man") is True + + def test_club_filter_fail(self) -> None: + p = Player(club="Liverpool") + assert p.matches_filter(club_filter="man") is False + + def test_no_filter_matches_all(self) -> None: + p = Player() + assert p.matches_filter() is True + + def test_combined_filters(self) -> None: + p = Player( + current_ability=170, + position="AMC", + nationality="Brazil", + club="Real Madrid", + attributes={"Dribbling": 18}, + ) + assert p.matches_filter( + min_attrs={"Dribbling": 15}, + min_ca=150, + position_filter="amc", + nationality_filter="bra", + club_filter="real", + )